feat(agent): refine pico core task capabilities

This commit is contained in:
Mario Zechner 2026-09-12 22:31:16 +02:00
parent d11c4b7281
commit 1915b35a9b
5 changed files with 283 additions and 200 deletions

View file

@ -12,17 +12,17 @@ export interface Address<T extends JsonValue = JsonValue> {
readonly scope: Scope;
readonly namespace: string;
readonly key?: string;
readonly collection: "value" | "list";
readonly kind: "value" | "list";
readonly rewind: boolean;
readonly [addressType]?: T;
}
export interface Value<T extends JsonValue> extends Address<T> {
readonly collection: "value";
readonly kind: "value";
}
export interface List<T extends JsonValue> extends Address<T> {
readonly collection: "list";
readonly kind: "list";
}
export interface Element<T extends JsonValue> {
@ -51,7 +51,7 @@ export function defineValue<T extends JsonValue>(scope: Scope, namespace: string
...options,
scope,
namespace,
collection: "value",
kind: "value",
rewind: options?.rewind ?? false,
});
}
@ -71,7 +71,7 @@ export function defineList<T extends JsonValue>(scope: Scope, namespace: string,
...options,
scope,
namespace,
collection: "list",
kind: "list",
rewind: options?.rewind ?? false,
});
}

View file

@ -6,3 +6,4 @@ export interface JsonObject {
}
export type Id = number;
export type Seq = number;

View file

@ -4,6 +4,7 @@ import type { Element, List, Value } from "./addresses.ts";
import type { Id, JsonValue } from "./core.ts";
import type { ContextEdit, Conversation, Entry, EntryInput, EntryKind } from "./entries.ts";
import type {
AnyDefinedTaskKind,
AnyTaskKind,
Completion,
ExactJsonInput,
@ -17,29 +18,13 @@ import type {
TaskOutputRef,
} from "./tasks.ts";
export interface Cursor {
readonly after?: Id;
readonly before?: Id;
readonly limit: number;
}
export interface Page<T> {
readonly items: readonly T[];
readonly next?: Id;
readonly readAt: Id;
}
export interface ListQuery extends Cursor {
readonly at?: Id;
}
export interface TxReaders {
getConversation(id: Id): Promise<Conversation | undefined>;
getEntry(id: Id): Promise<Entry | undefined>;
getEntry<E extends Entry>(kind: EntryKind<E>, id: Id): Promise<E | undefined>;
getEntries(ids: readonly Id[]): Promise<ReadonlyMap<Id, Entry>>;
getTask(id: Id): Promise<Task | undefined>;
getTask<K extends AnyTaskKind>(kind: K, id: Id): Promise<TaskOf<K> | undefined>;
getTask<K extends AnyDefinedTaskKind>(kind: K, id: Id): Promise<TaskOf<K> | undefined>;
getTasks(ids: readonly Id[]): Promise<ReadonlyMap<Id, Task>>;
}
@ -50,7 +35,8 @@ export interface TxValue<T extends JsonValue> {
}
export interface TxList<T extends JsonValue> {
read(query: ListQuery): Promise<Page<Element<T>>>;
read(): Promise<readonly Element<T>[]>;
read(at: Id): Promise<readonly Element<T>[]>;
append(value: T): Id;
remove(id: Id): void;
clear(): void;
@ -87,7 +73,7 @@ export interface StoredEntryDraft {
}
export type QueuedInput =
| { readonly mode: "steer" | "followUp" | "nextRun"; readonly input: UserInput; readonly requestId?: string }
| { readonly mode: "steer" | "followUp"; readonly input: UserInput; readonly requestId?: string }
| { readonly mode: "write"; readonly entry: StoredEntryDraft; readonly requestId?: string };
export type InputResult =
@ -104,24 +90,46 @@ export type InputResult =
export type TerminalInputResult = Extract<InputResult, { status: "done" | "unanswered" }>;
export interface AcceptOptions {
export interface InternalAcceptOptions {
readonly input: UserInput;
readonly requestId?: string;
readonly whenBusy?: "followUp" | "steer" | "reject";
}
export interface BaseTaskTx<C extends TaskCheckpoint> extends TxReaders {
checkpoint(value: C): void;
task<K extends AnyTaskKind, S extends TaskSpec<InputOf<K>, OutputOf<K>>>(
export interface SendInput {
readonly requestId?: string;
readonly content: UserInput;
readonly whenBusy?: "followUp" | "steer" | "reject";
}
export interface InputHandle {
readonly id: Id;
readonly conversationId: Id;
result(ctx: Context): Promise<InputResult | undefined>;
wait(ctx: Context): Promise<TerminalInputResult>;
abort(ctx: Context): Promise<"aborted" | "already_placed" | "not_found">;
}
interface TaskCreator<Kinds extends AnyDefinedTaskKind> {
task<K extends Kinds, S extends TaskSpec<InputOf<K>, OutputOf<K>>>(
kind: K,
spec: NoExtra<TaskSpec<InputOf<K>, OutputOf<K>>, S> & {
readonly input: ExactJsonInput<InputOf<K>, S["input"]>;
},
): Id;
}
interface TaskTxBase<C extends TaskCheckpoint> extends TxReaders {
checkpoint(value: C): void;
createConversation(spec: ConversationCreateSpec): Id;
value<T extends JsonValue>(address: Value<T>): TxValue<T>;
list<T extends JsonValue>(address: List<T>): TxList<T>;
accept(conversationId: Id, options: AcceptOptions): Promise<Acceptance>;
}
export type BaseTaskTx<C extends TaskCheckpoint> = TaskTxBase<C> & TaskCreator<AnyTaskKind>;
export interface InternalAdmissionTx {
accept(conversationId: Id, options: InternalAcceptOptions): Promise<Acceptance>;
queueInput(conversationId: Id, input: QueuedInput): Promise<Acceptance>;
write<E extends Entry>(
conversationId: Id,
@ -131,18 +139,22 @@ export interface BaseTaskTx<C extends TaskCheckpoint> extends TxReaders {
): Promise<Acceptance>;
}
export interface TurnTaskTx<C extends TaskCheckpoint> extends BaseTaskTx<C> {
entry<E extends Entry>(kind: EntryKind<E>, conversationId: Id, input: EntryInput<E>): Id;
}
export type CoreTaskTx<C extends TaskCheckpoint> = TaskTxBase<C> &
TaskCreator<AnyDefinedTaskKind> &
InternalAdmissionTx & {
entry<E extends Entry>(kind: EntryKind<E>, conversationId: Id, input: EntryInput<E>): Id;
};
export interface PublicValue<T extends JsonValue> {
get(at: Id | undefined, ctx: Context): Promise<T | undefined>;
get(ctx: Context): Promise<T | undefined>;
get(at: Id, ctx: Context): Promise<T | undefined>;
set(value: T, ctx: Context): Promise<void>;
delete(ctx: Context): Promise<void>;
}
export interface PublicList<T extends JsonValue> {
read(query: ListQuery, ctx: Context): Promise<Page<Element<T>>>;
read(ctx: Context): Promise<readonly Element<T>[]>;
read(at: Id, ctx: Context): Promise<readonly Element<T>[]>;
append(value: T, ctx: Context): Promise<Id>;
remove(id: Id, ctx: Context): Promise<void>;
clear(ctx: Context): Promise<void>;
@ -160,11 +172,11 @@ export interface ReadonlyTaskOutput<O extends object> {
read(ctx: Context): Promise<O>;
}
type RuntimeOutput<O extends object> = [O] extends [never] ? unknown : { readonly output: TaskOutput<O> };
type AbortRuntimeOutput<O extends object> = [O] extends [never] ? unknown : { readonly output: ReadonlyTaskOutput<O> };
type FinalOutput<O extends object> = [O] extends [never] ? unknown : { readonly output: O };
export type RuntimeOutput<O extends object> = [O] extends [never] ? unknown : { readonly output: TaskOutput<O> };
export type AbortRuntimeOutput<O extends object> = [O] extends [never]
? unknown
: { readonly output: ReadonlyTaskOutput<O> };
export type FinalOutput<O extends object> = [O] extends [never] ? unknown : { readonly output: O };
export interface ScratchTx {
value<T extends JsonValue>(address: Value<T>): TxValue<T>;
@ -178,38 +190,19 @@ export interface ScratchReader {
export interface TaskConversation {
readonly id: Id;
accept(options: AcceptOptions, ctx: Context): Promise<Acceptance>;
queueInput(input: QueuedInput, ctx: Context): Promise<Acceptance>;
write<E extends Entry>(
kind: EntryKind<E>,
input: EntryInput<E>,
requestId: string | undefined,
ctx: Context,
): Promise<Acceptance>;
result(inputId: Id, ctx: Context): Promise<InputResult | undefined>;
waitForInput(inputId: Id, ctx: Context): Promise<TerminalInputResult>;
abortInput(inputId: Id, ctx: Context): Promise<"aborted" | "already_placed" | "not_found">;
send(input: SendInput, ctx: Context): Promise<InputHandle>;
value<T extends JsonValue>(address: Value<T>): PublicValue<T>;
list<T extends JsonValue>(address: List<T>): PublicList<T>;
}
export interface AbortTaskConversation {
readonly id: Id;
write<E extends Entry>(
kind: EntryKind<E>,
input: EntryInput<E>,
requestId: string | undefined,
ctx: Context,
): Promise<Acceptance>;
result(inputId: Id, ctx: Context): Promise<InputResult | undefined>;
waitForInput(inputId: Id, ctx: Context): Promise<TerminalInputResult>;
value<T extends JsonValue>(address: Value<T>): PublicValue<T>;
list<T extends JsonValue>(address: List<T>): PublicList<T>;
}
export interface BaseTaskRuntime<I extends JsonValue, C extends TaskCheckpoint, O extends object = never> {
interface TaskRuntimeBase {
readonly taskId: Id;
commit<T>(build: (tx: BaseTaskTx<C>, current: RunningTask<I, C, O>) => T | Promise<T>, ctx: Context): Promise<T>;
scratch<T>(build: (tx: ScratchTx) => T | Promise<T>, ctx: Context): Promise<T>;
conversation(id: Id, ctx: Context): Promise<TaskConversation | undefined>;
waitForTask(id: Id, ctx: Context): Promise<Task>;
@ -218,68 +211,83 @@ export interface BaseTaskRuntime<I extends JsonValue, C extends TaskCheckpoint,
sleep(untilMs: number, ctx: Context): Promise<void>;
}
export interface TurnTaskRuntime<I extends JsonValue, C extends TaskCheckpoint, O extends object = never>
extends Omit<BaseTaskRuntime<I, C, O>, "commit"> {
commit<T>(build: (tx: TurnTaskTx<C>, current: RunningTask<I, C, O>) => T | Promise<T>, ctx: Context): Promise<T>;
interface CommitRuntime<I extends JsonValue, C extends TaskCheckpoint, O extends object, Tx> {
commit<T>(build: (tx: Tx, current: RunningTask<I, C, O>) => T | Promise<T>, ctx: Context): Promise<T>;
}
export type RuntimeFor<
export type TaskRuntime<I extends JsonValue, C extends TaskCheckpoint, O extends object = never> = TaskRuntimeBase &
CommitRuntime<I, C, O, BaseTaskTx<C>> &
RuntimeOutput<O>;
export type CoreTaskRuntime<I extends JsonValue, C extends TaskCheckpoint, O extends object = never> = TaskRuntimeBase &
CommitRuntime<I, C, O, CoreTaskTx<C>> &
RuntimeOutput<O>;
type TerminalClosureFor<
Tx,
I extends JsonValue,
C extends TaskCheckpoint,
R extends JsonValue,
F extends JsonValue,
O extends object,
T extends false | true,
> = (T extends true ? TurnTaskRuntime<I, C, O> : BaseTaskRuntime<I, C, O>) & RuntimeOutput<O>;
export interface TerminalTx<C extends TaskCheckpoint> extends BaseTaskTx<C> {}
export interface TurnTerminalTx<C extends TaskCheckpoint> extends TurnTaskTx<C> {}
export type FinalTx<C extends TaskCheckpoint, O extends object, T extends false | true> = (T extends true
? TurnTerminalTx<C>
: TerminalTx<C>) &
FinalOutput<O>;
> = (tx: Tx & FinalOutput<O>, current: RunningTask<I, C, O>) => Completion<R, F> | Promise<Completion<R, F>>;
export type FinalTx<C extends TaskCheckpoint, O extends object> = BaseTaskTx<C> & FinalOutput<O>;
export type CoreFinalTx<C extends TaskCheckpoint, O extends object> = CoreTaskTx<C> & FinalOutput<O>;
export type TerminalClosure<
I extends JsonValue,
C extends TaskCheckpoint,
R extends JsonValue,
F extends JsonValue,
O extends object,
T extends false | true,
> = (tx: FinalTx<C, O, T>, current: RunningTask<I, C, O>) => Completion<R, F> | Promise<Completion<R, F>>;
> = TerminalClosureFor<BaseTaskTx<C>, I, C, R, F, O>;
export type CoreTerminalClosure<
I extends JsonValue,
C extends TaskCheckpoint,
R extends JsonValue,
F extends JsonValue,
O extends object,
> = TerminalClosureFor<CoreTaskTx<C>, I, C, R, F, O>;
export interface AbortTx<C extends TaskCheckpoint> extends TxReaders {
checkpoint(value: C): void;
value<T extends JsonValue>(address: Value<T>): TxValue<T>;
list<T extends JsonValue>(address: List<T>): TxList<T>;
markTask(id: Id): Promise<"marked" | "terminal">;
}
export interface CoreAbortTx<C extends TaskCheckpoint> extends AbortTx<C> {
entry<E extends Entry>(kind: EntryKind<E>, conversationId: Id, input: EntryInput<E>): Id;
write<E extends Entry>(
conversationId: Id,
kind: EntryKind<E>,
input: EntryInput<E>,
requestId?: string,
): Promise<Acceptance>;
markTask(id: Id): Promise<"marked" | "terminal">;
}
export interface TurnAbortTx<C extends TaskCheckpoint> extends AbortTx<C> {
entry<E extends Entry>(kind: EntryKind<E>, conversationId: Id, input: EntryInput<E>): Id;
}
export type AbortFinalTx<C extends TaskCheckpoint, O extends object, T extends false | true> = (T extends true
? TurnAbortTx<C>
: AbortTx<C>) &
FinalOutput<O>;
type AbortClosureFor<Tx, I extends JsonValue, C extends TaskCheckpoint, A extends JsonValue, O extends object> = (
tx: Tx & FinalOutput<O>,
current: RunningTask<I, C, O>,
) => A | Promise<A>;
export type AbortFinalTx<C extends TaskCheckpoint, O extends object> = AbortTx<C> & FinalOutput<O>;
export type CoreAbortFinalTx<C extends TaskCheckpoint, O extends object> = CoreAbortTx<C> & FinalOutput<O>;
export type AbortClosure<
I extends JsonValue,
C extends TaskCheckpoint,
A extends JsonValue,
O extends object,
T extends false | true,
> = (tx: AbortFinalTx<C, O, T>, current: RunningTask<I, C, O>) => A | Promise<A>;
> = AbortClosureFor<AbortTx<C>, I, C, A, O>;
export type CoreAbortClosure<
I extends JsonValue,
C extends TaskCheckpoint,
A extends JsonValue,
O extends object,
> = AbortClosureFor<CoreAbortTx<C>, I, C, A, O>;
export interface BaseAbortRuntime<I extends JsonValue, C extends TaskCheckpoint, O extends object = never> {
interface AbortRuntimeBase {
readonly taskId: Id;
commit<T>(build: (tx: AbortTx<C>, current: RunningTask<I, C, O>) => T | Promise<T>, ctx: Context): Promise<T>;
scratch<T>(read: (scratch: ScratchReader) => T | Promise<T>, ctx: Context): Promise<T>;
conversation(id: Id, ctx: Context): Promise<AbortTaskConversation | undefined>;
waitForTask(id: Id, ctx: Context): Promise<Task>;
@ -287,14 +295,14 @@ export interface BaseAbortRuntime<I extends JsonValue, C extends TaskCheckpoint,
now(): number;
}
export interface TurnAbortRuntime<I extends JsonValue, C extends TaskCheckpoint, O extends object = never>
extends Omit<BaseAbortRuntime<I, C, O>, "commit"> {
commit<T>(build: (tx: TurnAbortTx<C>, current: RunningTask<I, C, O>) => T | Promise<T>, ctx: Context): Promise<T>;
}
export type AbortRuntimeFor<
export type AbortTaskRuntime<
I extends JsonValue,
C extends TaskCheckpoint,
O extends object,
T extends false | true,
> = (T extends true ? TurnAbortRuntime<I, C, O> : BaseAbortRuntime<I, C, O>) & AbortRuntimeOutput<O>;
O extends object = never,
> = AbortRuntimeBase & CommitRuntime<I, C, O, AbortTx<C>> & AbortRuntimeOutput<O>;
export type CoreAbortTaskRuntime<
I extends JsonValue,
C extends TaskCheckpoint,
O extends object = never,
> = AbortRuntimeBase & CommitRuntime<I, C, O, CoreAbortTx<C>> & AbortRuntimeOutput<O>;

View file

@ -1,6 +1,15 @@
import type { Context } from "@earendil-works/chord";
import type { Id, JsonObject, JsonValue } from "./core.ts";
import type { AbortClosure, AbortRuntimeFor, RuntimeFor, TerminalClosure } from "./runtime.ts";
import type {
AbortClosure,
AbortTaskRuntime,
CoreAbortClosure,
CoreAbortTaskRuntime,
CoreTaskRuntime,
CoreTerminalClosure,
TaskRuntime,
TerminalClosure,
} from "./runtime.ts";
export interface TaskCheckpoint extends JsonObject {
readonly phase: string;
@ -35,7 +44,7 @@ export interface TaskOutputRef<O extends object> extends StoredTaskOutputRef {
export type TaskOutputField<O extends object> = [O] extends [never] ? unknown : { readonly output: TaskOutputRef<O> };
type TaskOutputDefinition<I extends JsonValue, O extends object> = [O] extends [never]
export type TaskOutputDefinition<I extends JsonValue, O extends object> = [O] extends [never]
? { readonly output?: never }
: { readonly output: TaskOutputSpec<I, O> };
@ -53,7 +62,6 @@ export interface TaskBase<I extends JsonValue, C extends TaskCheckpoint> {
readonly checkpoint?: C;
readonly after: readonly Id[];
readonly background?: true;
readonly turn?: true;
readonly owns: readonly Id[];
readonly output?: StoredTaskOutputRef;
readonly abort?: true;
@ -88,8 +96,12 @@ const taskKindBrand: unique symbol = Symbol("pico.taskKind");
export interface TaskKindBase {
readonly kind: string;
readonly turn: false | true;
readonly [taskKindBrand]: true;
readonly [taskKindBrand]: "ordinary";
}
export interface CoreTaskKindBase {
readonly kind: string;
readonly [taskKindBrand]: "core";
}
interface TaskKindMethods<
@ -99,24 +111,47 @@ interface TaskKindMethods<
F extends JsonValue,
A extends JsonValue,
O extends object,
T extends false | true,
> extends TaskKindBase {
readonly turn: T;
execute(
task: RunningTask<I, C, O>,
runtime: RuntimeFor<I, C, O, T>,
runtime: TaskRuntime<I, C, O>,
ctx: Context,
): Promise<TerminalClosure<I, C, R, F, O, T>>;
): Promise<TerminalClosure<I, C, R, F, O>>;
recover(
task: RunningTask<I, C, O>,
runtime: RuntimeFor<I, C, O, T>,
runtime: TaskRuntime<I, C, O>,
ctx: Context,
): Promise<TerminalClosure<I, C, R, F, O, T>>;
): Promise<TerminalClosure<I, C, R, F, O>>;
abort(
task: RunningTask<I, C, O>,
runtime: AbortRuntimeFor<I, C, O, T>,
runtime: AbortTaskRuntime<I, C, O>,
ctx: Context,
): Promise<AbortClosure<I, C, A, O, T>>;
): Promise<AbortClosure<I, C, A, O>>;
}
interface CoreTaskKindMethods<
I extends JsonValue,
C extends TaskCheckpoint,
R extends JsonValue,
F extends JsonValue,
A extends JsonValue,
O extends object,
> extends CoreTaskKindBase {
execute(
task: RunningTask<I, C, O>,
runtime: CoreTaskRuntime<I, C, O>,
ctx: Context,
): Promise<CoreTerminalClosure<I, C, R, F, O>>;
recover(
task: RunningTask<I, C, O>,
runtime: CoreTaskRuntime<I, C, O>,
ctx: Context,
): Promise<CoreTerminalClosure<I, C, R, F, O>>;
abort(
task: RunningTask<I, C, O>,
runtime: CoreAbortTaskRuntime<I, C, O>,
ctx: Context,
): Promise<CoreAbortClosure<I, C, A, O>>;
}
export type TaskKind<
@ -126,8 +161,16 @@ export type TaskKind<
F extends JsonValue,
A extends JsonValue,
O extends object = never,
T extends false | true = false,
> = TaskKindMethods<I, C, R, F, A, O, T> & TaskOutputDefinition<I, O>;
> = TaskKindMethods<I, C, R, F, A, O> & TaskOutputDefinition<I, O>;
export type CoreTaskKind<
I extends JsonValue,
C extends TaskCheckpoint,
R extends JsonValue,
F extends JsonValue,
A extends JsonValue,
O extends object = never,
> = CoreTaskKindMethods<I, C, R, F, A, O> & TaskOutputDefinition<I, O>;
export type NoExtra<Expected, Actual extends Expected> = Actual & Record<Exclude<keyof Actual, keyof Expected>, never>;
@ -137,39 +180,26 @@ export type ExactJsonInput<Expected extends JsonValue, Actual extends Expected>
? Actual & Record<Exclude<keyof Actual, keyof Expected>, never>
: Actual;
export type NonTurnKindDefinition<
I extends JsonValue,
C extends TaskCheckpoint,
R extends JsonValue,
F extends JsonValue,
A extends JsonValue,
O extends object,
> = Omit<TaskKind<I, C, R, F, A, O, false>, "turn" | typeof taskKindBrand> & { readonly turn?: false };
type KindDefinition<K> = Omit<K, typeof taskKindBrand>;
type KindFactory<K> = <D extends KindDefinition<K>>(definition: NoExtra<KindDefinition<K>, D>) => D & K;
export type TurnKindDefinition<
export type TaskKindFactory<
I extends JsonValue,
C extends TaskCheckpoint,
R extends JsonValue,
F extends JsonValue,
A extends JsonValue,
O extends object,
> = Omit<TaskKind<I, C, R, F, A, O, true>, typeof taskKindBrand>;
> = KindFactory<TaskKind<I, C, R, F, A, O>>;
export interface TaskKindFactory<
export type CoreTaskKindFactory<
I extends JsonValue,
C extends TaskCheckpoint,
R extends JsonValue,
F extends JsonValue,
A extends JsonValue,
O extends object,
> {
<D extends NonTurnKindDefinition<I, C, R, F, A, O>>(
definition: NoExtra<NonTurnKindDefinition<I, C, R, F, A, O>, D>,
): D & TaskKind<I, C, R, F, A, O, false>;
<D extends TurnKindDefinition<I, C, R, F, A, O>>(
definition: NoExtra<TurnKindDefinition<I, C, R, F, A, O>, D>,
): D & TaskKind<I, C, R, F, A, O, true>;
}
> = KindFactory<CoreTaskKind<I, C, R, F, A, O>>;
export function defineTask<
I extends JsonValue,
@ -179,29 +209,49 @@ export function defineTask<
A extends JsonValue,
O extends object = never,
>(): TaskKindFactory<I, C, R, F, A, O> {
const define = (definition: NonTurnKindDefinition<I, C, R, F, A, O> | TurnKindDefinition<I, C, R, F, A, O>) =>
const define = (definition: KindDefinition<TaskKind<I, C, R, F, A, O>>) =>
Object.freeze({
...definition,
turn: definition.turn ?? false,
[taskKindBrand]: true as const,
[taskKindBrand]: "ordinary" as const,
});
return define as TaskKindFactory<I, C, R, F, A, O>;
}
/** Internal authoring helper for Pico's fixed privileged task kinds. */
export function defineCoreTask<
I extends JsonValue,
C extends TaskCheckpoint,
R extends JsonValue,
F extends JsonValue,
A extends JsonValue,
O extends object = never,
>(): CoreTaskKindFactory<I, C, R, F, A, O> {
const define = (definition: KindDefinition<CoreTaskKind<I, C, R, F, A, O>>) =>
Object.freeze({
...definition,
[taskKindBrand]: "core" as const,
});
return define as CoreTaskKindFactory<I, C, R, F, A, O>;
}
export type AnyTaskKind = TaskKindBase;
export type PayloadsOf<K> = K extends TaskKind<infer I, infer C, infer R, infer F, infer A, infer O, false | true>
export type AnyCoreTaskKind = CoreTaskKindBase;
export type AnyDefinedTaskKind = AnyTaskKind | AnyCoreTaskKind;
export type PayloadsOf<K> = K extends TaskKind<infer I, infer C, infer R, infer F, infer A, infer O>
? { input: I; checkpoint: C; result: R; failure: F; aborted: A; output: O }
: never;
: K extends CoreTaskKind<infer I, infer C, infer R, infer F, infer A, infer O>
? { input: I; checkpoint: C; result: R; failure: F; aborted: A; output: O }
: never;
export type InputOf<K> = PayloadsOf<K>["input"];
export type CheckpointOf<K> = PayloadsOf<K>["checkpoint"];
export type ResultOf<K> = PayloadsOf<K>["result"];
export type FailureOf<K> = PayloadsOf<K>["failure"];
export type AbortedOf<K> = PayloadsOf<K>["aborted"];
export type OutputOf<K> = PayloadsOf<K>["output"];
export type TurnOf<K extends TaskKindBase> = K["turn"];
type WithoutOutput<T> = T extends unknown ? Omit<T, "output"> : never;
export type TaskOf<K extends TaskKindBase> = WithoutOutput<
export type TaskOf<K extends AnyDefinedTaskKind> = WithoutOutput<
Task<InputOf<K>, CheckpointOf<K>, ResultOf<K>, FailureOf<K>, AbortedOf<K>>
> &
TaskOutputField<OutputOf<K>>;

View file

@ -2,11 +2,14 @@ import type { Context } from "@earendil-works/chord";
import type {
AbortClosure,
AbortedOf,
AbortRuntimeFor,
BaseAbortRuntime,
BaseTaskRuntime,
AbortTaskRuntime,
BaseTaskTx,
CheckpointOf,
CoreAbortClosure,
CoreAbortTaskRuntime,
CoreTaskRuntime,
CoreTaskTx,
CoreTerminalClosure,
Entry,
EntryInput,
ExactJsonInput,
@ -18,19 +21,24 @@ import type {
PayloadsOf,
ReadonlyTaskOutput,
ResultOf,
RuntimeFor,
SummaryEntry,
TaskKindBase,
TaskOf,
TaskOutcome,
TaskOutput,
TaskOutputRef,
TaskRuntime,
TerminalClosure,
TurnAbortRuntime,
TurnOf,
TurnTaskRuntime,
Value,
} from "../../../src/harness/pico/index.ts";
import { defineEntry, defineList, defineTask, defineTaskOutput, defineValue } from "../../../src/harness/pico/index.ts";
import {
defineCoreTask,
defineEntry,
defineList,
defineTask,
defineTaskOutput,
defineValue,
} from "../../../src/harness/pico/index.ts";
type Equal<Left, Right> = (<Type>() => Type extends Left ? 1 : 2) extends <Type>() => Type extends Right ? 1 : 2
? (<Type>() => Type extends Right ? 1 : 2) extends <Type>() => Type extends Left ? 1 : 2
@ -69,8 +77,8 @@ type JsonAssertions = [
Assert<{ readonly value: undefined } extends JsonValue ? false : true>,
];
const nonTurnKind = defineTask<TestInput, TestCheckpoint, TestResult, TestFailure, TestAborted>()({
kind: "test.non_turn",
const ordinaryKind = defineTask<TestInput, TestCheckpoint, TestResult, TestFailure, TestAborted>()({
kind: "test.ordinary",
execute() {
return Promise.resolve(() => ({ status: "completed", result: { answer: "done" } }));
},
@ -82,9 +90,8 @@ const nonTurnKind = defineTask<TestInput, TestCheckpoint, TestResult, TestFailur
},
});
const turnKind = defineTask<TestInput, TestCheckpoint, TestResult, TestFailure, TestAborted>()({
kind: "test.turn",
turn: true,
const coreKind = defineCoreTask<TestInput, TestCheckpoint, TestResult, TestFailure, TestAborted>()({
kind: "pi.test_core",
execute() {
return Promise.resolve(() => ({ status: "completed", result: { answer: "done" } }));
},
@ -148,55 +155,53 @@ const incompatibleCheckpointKind = defineTask<
},
});
type NonTurnRuntimeTx = Parameters<Parameters<RuntimeFor<TestInput, TestCheckpoint, never, false>["commit"]>[0]>[0];
type TurnRuntimeTx = Parameters<Parameters<RuntimeFor<TestInput, TestCheckpoint, never, true>["commit"]>[0]>[0];
type NonTurnAbortTx = Parameters<Parameters<AbortRuntimeFor<TestInput, TestCheckpoint, never, false>["commit"]>[0]>[0];
type TurnRuntimeAbortTx = Parameters<
Parameters<AbortRuntimeFor<TestInput, TestCheckpoint, never, true>["commit"]>[0]
>[0];
type NonTurnFinalTx = Parameters<TerminalClosure<TestInput, TestCheckpoint, TestResult, TestFailure, never, false>>[0];
type TurnFinalTx = Parameters<TerminalClosure<TestInput, TestCheckpoint, TestResult, TestFailure, never, true>>[0];
type NonTurnAbortFinalTx = Parameters<AbortClosure<TestInput, TestCheckpoint, TestAborted, never, false>>[0];
type TurnAbortFinalTx = Parameters<AbortClosure<TestInput, TestCheckpoint, TestAborted, never, true>>[0];
type OutputFinalTx = Parameters<
TerminalClosure<TestInput, TestCheckpoint, TestResult, TestFailure, TestOutput, false>
>[0];
type OutputAbortFinalTx = Parameters<AbortClosure<TestInput, TestCheckpoint, TestAborted, TestOutput, false>>[0];
type OrdinaryRuntimeTx = Parameters<Parameters<TaskRuntime<TestInput, TestCheckpoint>["commit"]>[0]>[0];
type CoreRuntimeTx = Parameters<Parameters<CoreTaskRuntime<TestInput, TestCheckpoint>["commit"]>[0]>[0];
type OrdinaryAbortTx = Parameters<Parameters<AbortTaskRuntime<TestInput, TestCheckpoint>["commit"]>[0]>[0];
type CoreRuntimeAbortTx = Parameters<Parameters<CoreAbortTaskRuntime<TestInput, TestCheckpoint>["commit"]>[0]>[0];
type OrdinaryFinalTx = Parameters<TerminalClosure<TestInput, TestCheckpoint, TestResult, TestFailure, never>>[0];
type CoreFinalTx = Parameters<CoreTerminalClosure<TestInput, TestCheckpoint, TestResult, TestFailure, never>>[0];
type OrdinaryAbortFinalTx = Parameters<AbortClosure<TestInput, TestCheckpoint, TestAborted, never>>[0];
type CoreAbortFinalTx = Parameters<CoreAbortClosure<TestInput, TestCheckpoint, TestAborted, never>>[0];
type OutputFinalTx = Parameters<TerminalClosure<TestInput, TestCheckpoint, TestResult, TestFailure, TestOutput>>[0];
type OutputAbortFinalTx = Parameters<AbortClosure<TestInput, TestCheckpoint, TestAborted, TestOutput>>[0];
type KindAssertions = [
Assert<Equal<TurnOf<typeof nonTurnKind>, false>>,
Assert<Equal<TurnOf<typeof turnKind>, true>>,
Assert<Equal<InputOf<typeof nonTurnKind>, TestInput>>,
Assert<Equal<CheckpointOf<typeof nonTurnKind>, TestCheckpoint>>,
Assert<Equal<ResultOf<typeof nonTurnKind>, TestResult>>,
Assert<Equal<FailureOf<typeof nonTurnKind>, TestFailure>>,
Assert<Equal<AbortedOf<typeof nonTurnKind>, TestAborted>>,
Assert<Equal<PayloadsOf<typeof nonTurnKind>["aborted"], TestAborted>>,
Assert<Equal<OutputOf<typeof nonTurnKind>, never>>,
Assert<"turn" extends keyof typeof ordinaryKind ? false : true>,
Assert<"turn" extends keyof typeof coreKind ? false : true>,
Assert<"busy" extends keyof TaskOf<typeof ordinaryKind> ? false : true>,
Assert<Equal<InputOf<typeof ordinaryKind>, TestInput>>,
Assert<Equal<InputOf<typeof coreKind>, TestInput>>,
Assert<Equal<CheckpointOf<typeof ordinaryKind>, TestCheckpoint>>,
Assert<Equal<ResultOf<typeof ordinaryKind>, TestResult>>,
Assert<Equal<FailureOf<typeof ordinaryKind>, TestFailure>>,
Assert<Equal<AbortedOf<typeof ordinaryKind>, TestAborted>>,
Assert<Equal<PayloadsOf<typeof ordinaryKind>["aborted"], TestAborted>>,
Assert<Equal<OutputOf<typeof ordinaryKind>, never>>,
Assert<Equal<OutputOf<typeof outputTaskKind>, TestOutput>>,
Assert<Equal<Parameters<typeof nonTurnKind.execute>[1], BaseTaskRuntime<TestInput, TestCheckpoint>>>,
Assert<Equal<Parameters<typeof ordinaryKind.execute>[1], TaskRuntime<TestInput, TestCheckpoint>>>,
Assert<Equal<Parameters<typeof coreKind.execute>[1], CoreTaskRuntime<TestInput, TestCheckpoint>>>,
Assert<Equal<Parameters<typeof outputTaskKind.execute>[1]["output"], TaskOutput<TestOutput>>>,
Assert<Equal<ReturnType<Parameters<TaskOutput<TestOutput>["mutate"]>[0]>, undefined>>,
Assert<Equal<Parameters<typeof turnKind.execute>[1], TurnTaskRuntime<TestInput, TestCheckpoint>>>,
Assert<Equal<Parameters<typeof nonTurnKind.abort>[1], BaseAbortRuntime<TestInput, TestCheckpoint>>>,
Assert<Equal<Parameters<typeof turnKind.abort>[1], TurnAbortRuntime<TestInput, TestCheckpoint>>>,
Assert<Equal<Parameters<typeof ordinaryKind.abort>[1], AbortTaskRuntime<TestInput, TestCheckpoint>>>,
Assert<Equal<Parameters<typeof coreKind.abort>[1], CoreAbortTaskRuntime<TestInput, TestCheckpoint>>>,
Assert<Equal<Parameters<typeof outputTaskKind.abort>[1]["output"], ReadonlyTaskOutput<TestOutput>>>,
Assert<Equal<HasOutput<Parameters<typeof nonTurnKind.execute>[1]>, false>>,
Assert<Equal<HasOutput<Parameters<typeof ordinaryKind.execute>[1]>, false>>,
Assert<Equal<HasOutput<Parameters<typeof outputTaskKind.execute>[1]>, true>>,
Assert<Equal<OutputFinalTx["output"], TestOutput>>,
Assert<Equal<OutputAbortFinalTx["output"], TestOutput>>,
Assert<Equal<HasEntry<NonTurnRuntimeTx>, false>>,
Assert<Equal<HasEntry<TurnRuntimeTx>, true>>,
Assert<Equal<HasEntry<NonTurnFinalTx>, false>>,
Assert<Equal<HasEntry<TurnFinalTx>, true>>,
Assert<Equal<HasEntry<NonTurnAbortTx>, false>>,
Assert<Equal<HasEntry<TurnRuntimeAbortTx>, true>>,
Assert<Equal<HasEntry<NonTurnAbortFinalTx>, false>>,
Assert<Equal<HasEntry<TurnAbortFinalTx>, true>>,
Assert<"sleep" extends keyof BaseAbortRuntime<TestInput, TestCheckpoint> ? false : true>,
Assert<Equal<HasEntry<OrdinaryRuntimeTx>, false>>,
Assert<Equal<HasEntry<CoreRuntimeTx>, true>>,
Assert<Equal<HasEntry<OrdinaryFinalTx>, false>>,
Assert<Equal<HasEntry<CoreFinalTx>, true>>,
Assert<Equal<HasEntry<OrdinaryAbortTx>, false>>,
Assert<Equal<HasEntry<CoreRuntimeAbortTx>, true>>,
Assert<Equal<HasEntry<OrdinaryAbortFinalTx>, false>>,
Assert<Equal<HasEntry<CoreAbortFinalTx>, true>>,
Assert<"sleep" extends keyof AbortTaskRuntime<TestInput, TestCheckpoint> ? false : true>,
];
type KindOutcome = Extract<TaskOf<typeof nonTurnKind>, { readonly status: "terminal" }>["outcome"];
type KindOutcome = Extract<TaskOf<typeof ordinaryKind>, { readonly status: "terminal" }>["outcome"];
type OutcomeAssertions = [
Assert<Equal<KindOutcome, TaskOutcome<TestResult, TestFailure, TestAborted>>>,
Assert<Equal<Extract<KindOutcome, { readonly status: "completed" }>["result"], TestResult>>,
@ -220,9 +225,11 @@ function verifyTaskInputs(
outputRef: TaskOutputRef<TestOutput>,
wrongOutputRef: TaskOutputRef<{ count: number }>,
): void {
tx.task(nonTurnKind, {
tx.task(ordinaryKind, {
input: { prompt: "hello", nested: { count: 1 } },
});
// @ts-expect-error ordinary task transactions cannot instantiate fixed core task kinds
tx.task(coreKind, { input: { prompt: "core", nested: { count: 1 } } });
tx.task(outputTaskKind, {
input: { prompt: "own output", nested: { count: 1 } },
});
@ -231,7 +238,7 @@ function verifyTaskInputs(
output: outputRef,
});
// @ts-expect-error a no-output kind cannot receive an output reference
tx.task(nonTurnKind, { input: { prompt: "hello", nested: { count: 1 } }, output: outputRef });
tx.task(ordinaryKind, { input: { prompt: "hello", nested: { count: 1 } }, output: outputRef });
tx.task(outputTaskKind, {
input: { prompt: "wrong output", nested: { count: 1 } },
// @ts-expect-error a shared output reference must have the kind's output type
@ -243,30 +250,30 @@ function verifyTaskInputs(
input: { prompt: "hello", nested: { count: 1 } },
background: true,
} as const;
tx.task(nonTurnKind, exactVariable);
tx.task(ordinaryKind, exactVariable);
// Top-level exactness intentionally does not recursively reject extra nested fields.
tx.task(nonTurnKind, {
tx.task(ordinaryKind, {
input: { prompt: "hello", nested: { count: 1, extension: true } },
});
// @ts-expect-error task input literals reject extra top-level fields
tx.task(nonTurnKind, { input: { prompt: "hello", nested: { count: 1 }, extension: true } });
tx.task(ordinaryKind, { input: { prompt: "hello", nested: { count: 1 }, extension: true } });
const extraInputVariable = { prompt: "hello", nested: { count: 1 }, extension: true };
// @ts-expect-error task input variables reject visible extra top-level fields
tx.task(nonTurnKind, { input: extraInputVariable });
tx.task(ordinaryKind, { input: extraInputVariable });
const inputBase = { prompt: "hello", nested: { count: 1 } };
// @ts-expect-error task input spreads reject visible extra top-level fields
tx.task(nonTurnKind, { input: { ...inputBase, extension: true } });
tx.task(ordinaryKind, { input: { ...inputBase, extension: true } });
const extraSpecVariable = {
input: { prompt: "hello", nested: { count: 1 } },
priority: 1,
};
// @ts-expect-error task specs reject visible extra top-level fields
tx.task(nonTurnKind, extraSpecVariable);
tx.task(ordinaryKind, extraSpecVariable);
tx.checkpoint({ phase: "ready", cursor: 1 });
// @ts-expect-error every checkpoint requires its phase
@ -277,6 +284,21 @@ function verifyTaskInputs(
tx.checkpoint({ phase: "ready", token: "cursor" });
}
function verifyOrdinaryRegistration(register: (kind: TaskKindBase) => void): void {
register(ordinaryKind);
// @ts-expect-error fixed core kinds cannot enter the ordinary task registry
register(coreKind);
}
function verifyCoreTaskInputs(tx: CoreTaskTx<TestCheckpoint>): void {
tx.task(ordinaryKind, {
input: { prompt: "ordinary child", nested: { count: 1 } },
});
tx.task(coreKind, {
input: { prompt: "core child", nested: { count: 1 } },
});
}
function verifyOutputMutation(
output: TaskOutput<TestOutput>,
readonlyOutput: ReadonlyTaskOutput<TestOutput>,
@ -298,10 +320,10 @@ function verifyCheckpointCompatibility(
incompatible: CheckpointOf<typeof incompatibleCheckpointKind>,
): void {
// Checkpoints are structural: an independently declared identical shape is assignable.
const structurallyCompatible: CheckpointOf<typeof nonTurnKind> = checkpoint;
const structurallyCompatible: CheckpointOf<typeof ordinaryKind> = checkpoint;
void structurallyCompatible;
// @ts-expect-error a differently shaped kind checkpoint is not assignable
const structurallyIncompatible: CheckpointOf<typeof nonTurnKind> = incompatible;
const structurallyIncompatible: CheckpointOf<typeof ordinaryKind> = incompatible;
void structurallyIncompatible;
}
@ -373,6 +395,8 @@ void entryAssertions;
void exactInputAssertion;
void entry;
void verifyTaskInputs;
void verifyOrdinaryRegistration;
void verifyCoreTaskInputs;
void verifyOutputMutation;
void verifyCheckpointCompatibility;
void verifyOutcomes;