mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
feat(ai,agent,coding-agent): validate tool arguments with pi-schema, drop TypeBox
pi-schema: opt-in coercion, compile(schema, { coerce: true }) with coerce() and
parse(), the superset of TypeBox's Value.Convert rules and pi-ai's old coercion;
unresolvedRefs option; url and json-pointer-uri-fragment formats.
pi-ai: Tool takes any JSON Schema; validation through pi-schema; Type, Static
and TSchema are no longer exported (breaking). pi-agent-core and pi-durable:
tools on pi-schema, no TypeBox dependency. Coding agent: extensions keep Type,
Static, TSchema and StringEnum through an extension view of pi-ai; built-in
tools stay on TypeBox.
This commit is contained in:
parent
2c8d1feb14
commit
6536f80129
139 changed files with 5115 additions and 1305 deletions
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
### Breaking Changes
|
||||
|
||||
- Tool schemas are JSON Schema typed through `@earendil-works/pi-schema`: `defineTool()` and `ToolRegistration` take any JSON Schema object as `parameters` and `structuredOutputSchema`, and type `args` with pi-schema's `Static` (`ToolArgs<TParameters>`; other schemas give `unknown`). The built-in tools (`read`, `write`, `edit`, `bash`, `powershell`, `codemode`, `tool_search`, the model tools) are built with pi-schema, with the same JSON Schema up to key order. `structuredOutput` is validated with pi-schema, so `invalid_structured_output` messages use its wording. `typebox` is no longer a dependency. Migration: replace `Type.Object({ n: Type.Number() })` from `@earendil-works/pi-ai` with `obj({ n: num() })` from `@earendil-works/pi-schema` (closed by default; `{ open: true }` allows other properties).
|
||||
- `SqliteExecutor` (re-exported from Chord) gains `runCounted(sql, ...params)`, resolving with the rows the statement changed; custom `SqliteDatabase` implementations add it.
|
||||
- `Storage.commit()` stores a group of commits in one call: `commit(batches, receive, context)` resolves with a `CommitResult` (`{ stored, refused? }`) and replaces `commit(writes, adopt, context)`. Each batch is one atomic commit with its own seq, checked against the state the batches before it left. The first batch Durable's own checks refuse (global IDs, document actions, copy sources) is not stored, nor anything after it; the batches before it are, and the call resolves with their number in `stored` and the refusal in `refused`. Any other failure (a driver or I/O error, a failed `COMMIT`) rejects the whole call as an unknown outcome and stores nothing the backend could roll back. `receive(index, seq)` is called once per stored batch, in order, synchronously, once they are durable and before the next call starts; receipts made before a rejection stand. Every batch has at least one write. The optional `publishing(publish, context)` runs each commit's publication inside the storage's own scope (`StoreStorage`: the attached host's batch). Memory, SQLite, JSONL, IndexedDB and `StoreStorage` store a call in one transaction or append, and the storage conformance suite checks the contract. Migration for a custom Storage:
|
||||
|
||||
|
|
|
|||
|
|
@ -257,15 +257,15 @@ registry.install(createCodingTools({ images: await createNodePhotonImages() }));
|
|||
|
||||
A model without image input sees a placeholder instead of the image, and `read` says so in a diagnostic. `/images/node` prepares images in a worker thread, so a large photo (a second or two of work) never blocks the event loop; the worker exits after 30 s without images, freeing Photon's WebAssembly memory (`createNodePhotonImages({ worker: false, idleMs })`). Elsewhere, `createPhotonImages()` and `/images/cloudflare` work on the calling thread, and decoding takes the image's full size in WebAssembly memory, which does not shrink again; leave the processor out on memory-constrained hosts such as Cloudflare Workers unless images stay small.
|
||||
|
||||
Define your own tool with a TypeBox schema. `defineTool()` types `args` from `parameters`, which the Harness validates before `execute()`. `api.output()` streams running output, which becomes the result when `execute()` returns no `output`:
|
||||
Define your own tool with a JSON Schema, built here with `@earendil-works/pi-schema`. `defineTool()` types `args` from `parameters` (through pi-schema's `Static`; other JSON Schema gives `unknown`), which the Harness coerces and validates before `execute()`. `api.output()` streams running output, which becomes the result when `execute()` returns no `output`:
|
||||
|
||||
```typescript
|
||||
import { Type } from "@earendil-works/pi-ai";
|
||||
import { num, obj, str } from "@earendil-works/pi-schema";
|
||||
|
||||
const count = defineTool({
|
||||
name: "count",
|
||||
description: "Count from 1 to n",
|
||||
parameters: Type.Object({ n: Type.Number() }),
|
||||
parameters: obj({ n: num() }),
|
||||
execute: async (args, api) => {
|
||||
for (let i = 1; i <= args.n; i++) api.output(`${i}\n`);
|
||||
return {};
|
||||
|
|
@ -282,8 +282,8 @@ A result has three channels: `output` for the model, `structuredOutput` for prog
|
|||
const lines = defineTool({
|
||||
name: "lines",
|
||||
description: "Count the lines of a file",
|
||||
parameters: Type.Object({ path: Type.String() }),
|
||||
structuredOutputSchema: Type.Object({ lines: Type.Number() }),
|
||||
parameters: obj({ path: str() }),
|
||||
structuredOutputSchema: obj({ lines: num() }),
|
||||
execute: async (args, api, context) => {
|
||||
const text = await readText(api, args.path, context);
|
||||
const count = text.split("\n").length;
|
||||
|
|
@ -312,7 +312,7 @@ A tool calls another tool with `api.executeTool()`, as a code mode script or an
|
|||
const testsPass = defineTool({
|
||||
name: "tests_pass",
|
||||
description: "Run the test suite and say whether it passed",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api, context) => {
|
||||
const run = await api.executeTool("bash", { command: "npm test" }, context, { progress: false });
|
||||
// Absent when bash did not finish, for example on a timeout.
|
||||
|
|
@ -722,7 +722,7 @@ const Subagent: Extension = defineExtension({
|
|||
defineTool({
|
||||
name: "subagent",
|
||||
description: "Delegate a self-contained task to a subagent and get its answer back.",
|
||||
parameters: Type.Object({ task: Type.String() }),
|
||||
parameters: obj({ task: str() }),
|
||||
replay: "safe", // a rerun after a crash finds the same child and submission
|
||||
execute: async (args, api, context) => {
|
||||
const child = await api.commit(async (tx) => {
|
||||
|
|
@ -905,7 +905,7 @@ registerStorageConformance({ describe, expect, it }, "My Storage", async (use) =
|
|||
});
|
||||
```
|
||||
|
||||
The package root loads TypeBox, because the tool task validates arguments with pi-ai's `validateToolArguments()`. That costs about 23 MB of peak RSS unbundled, about 4 MB in a tree-shaken bundle.
|
||||
The package root loads pi-schema, because the tool task validates arguments with pi-ai's `validateToolArguments()`.
|
||||
|
||||
## Errors
|
||||
|
||||
|
|
|
|||
|
|
@ -161,10 +161,10 @@
|
|||
"@earendil-works/chord": "1.4.0",
|
||||
"@earendil-works/pi-ai": "1.4.0",
|
||||
"@earendil-works/pi-codemode": "1.4.0",
|
||||
"@earendil-works/pi-schema": "1.4.0",
|
||||
"@earendil-works/pi-telemetry": "1.4.0",
|
||||
"@silvia-odwyer/photon": "0.3.3",
|
||||
"diff": "8.0.4",
|
||||
"typebox": "1.3.27"
|
||||
"diff": "8.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/vitest-plugin": "1.4.10",
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ import type {
|
|||
CodemodeSandboxBase,
|
||||
} from "@earendil-works/pi-codemode/portable";
|
||||
import { CODEMODE_SOURCE_GRAMMAR, parseCodemodeSource } from "@earendil-works/pi-codemode/source";
|
||||
import { type Static, Type } from "typebox";
|
||||
import { obj, type Static, str } from "@earendil-works/pi-schema";
|
||||
import { defineDoc } from "../documents.ts";
|
||||
import type { ExecutionEnv } from "../env/index.ts";
|
||||
import { errorMessage } from "../errors.ts";
|
||||
|
|
@ -210,7 +210,7 @@ export type CodemodeGlobal = {
|
|||
execute(args: unknown, api: ToolExecutionApi, context: Context): Promise<JsonValue | undefined>;
|
||||
};
|
||||
|
||||
const codemodeSchema = Type.Object({ code: Type.String({ description: "Raw JavaScript source." }) });
|
||||
const codemodeSchema = obj({ code: str({ description: "Raw JavaScript source." }) }, { open: true });
|
||||
|
||||
export type CodemodeToolInput = Static<typeof codemodeSchema>;
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import type {
|
|||
ModelTypeMap,
|
||||
Usage,
|
||||
} from "@earendil-works/pi-ai";
|
||||
import { Type } from "typebox";
|
||||
import { obj, unknown } from "@earendil-works/pi-schema";
|
||||
import { defineTool } from "../harness/define.ts";
|
||||
import type { ToolRegistration } from "../harness/types.ts";
|
||||
|
||||
|
|
@ -227,7 +227,7 @@ function resolveModel<TType extends "classifier" | "image">(
|
|||
);
|
||||
}
|
||||
|
||||
const ModelCallParameters = Type.Object({ model: Type.Unknown(), context: Type.Unknown() });
|
||||
const ModelCallParameters = obj({ model: unknown(), context: unknown() }, { open: true });
|
||||
|
||||
/** The result of a model call as the tool returns it: the whole result for the script, usage counted by the Harness. */
|
||||
function modelResult(
|
||||
|
|
@ -255,7 +255,7 @@ export function createModelTools(): ToolRegistration[] {
|
|||
name: CLASSIFY_TOOL,
|
||||
description: "models.classify() of code mode scripts.",
|
||||
parameters: ModelCallParameters,
|
||||
structuredOutputSchema: Type.Unknown(),
|
||||
structuredOutputSchema: unknown(),
|
||||
callers: ["tools"],
|
||||
execute: async (args, api, context) => {
|
||||
const model = resolveModel("models.classify", "classifier", args.model, api.models);
|
||||
|
|
@ -271,7 +271,7 @@ export function createModelTools(): ToolRegistration[] {
|
|||
name: GENERATE_IMAGES_TOOL,
|
||||
description: "models.generateImages() of code mode scripts.",
|
||||
parameters: ModelCallParameters,
|
||||
structuredOutputSchema: Type.Unknown(),
|
||||
structuredOutputSchema: unknown(),
|
||||
callers: ["tools"],
|
||||
execute: async (args, api, context) => {
|
||||
const model = resolveModel("models.generateImages", "image", args.model, api.models);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import type { JsonValue } from "@earendil-works/chord";
|
||||
import type { TSchema } from "@earendil-works/pi-ai";
|
||||
import type { Task } from "../types.ts";
|
||||
import type {
|
||||
AnyTask,
|
||||
|
|
@ -20,7 +19,7 @@ export function defineExtension<Tool extends ToolRegistration = ToolRegistration
|
|||
}
|
||||
|
||||
/** Identity function that types a tool: `args` from `parameters`, details from what it reports. */
|
||||
export function defineTool<TParameters extends TSchema, TDetails extends JsonValue = JsonValue>(
|
||||
export function defineTool<TParameters extends object, TDetails extends JsonValue = JsonValue>(
|
||||
tool: ToolRegistration<TParameters, TDetails>,
|
||||
): ToolRegistration<TParameters, TDetails> {
|
||||
return tool;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ import {
|
|||
} from "@earendil-works/chord/output";
|
||||
import type { ImageContent, TextContent, ToolCall, ToolResultMessage } from "@earendil-works/pi-ai";
|
||||
import { validateToolArguments } from "@earendil-works/pi-ai/utils/validation";
|
||||
import { Compile } from "typebox/compile";
|
||||
import { compile, type Validator } from "@earendil-works/pi-schema";
|
||||
import { formats } from "@earendil-works/pi-schema/formats";
|
||||
import { defineDocFamily } from "../documents.ts";
|
||||
import { AssistantEntry, ToolResultEntry } from "../entries.ts";
|
||||
import type { ExecutionEnv } from "../env/index.ts";
|
||||
|
|
@ -770,7 +771,7 @@ async function afterHarnessResult(
|
|||
return { ...rest, ...value, output: bounded.content, diagnostics: [...(rest.diagnostics ?? []), ...harness] };
|
||||
}
|
||||
|
||||
const structuredValidators = new WeakMap<object, ReturnType<typeof Compile>>();
|
||||
const structuredValidators = new WeakMap<object, Validator>();
|
||||
|
||||
/** Why a result's `structuredOutput` breaks the tool's contract, or `undefined` when it keeps it. */
|
||||
function structuredOutputError(tool: ToolRegistration, result: ToolExecutionResult): string | undefined {
|
||||
|
|
@ -786,12 +787,17 @@ function structuredOutputError(tool: ToolRegistration, result: ToolExecutionResu
|
|||
}
|
||||
let validator = structuredValidators.get(schema);
|
||||
if (validator === undefined) {
|
||||
validator = Compile(schema);
|
||||
validator = compile(schema, { formats, unresolvedRefs: "reject" });
|
||||
structuredValidators.set(schema, validator);
|
||||
}
|
||||
if (validator.Check(value)) return undefined;
|
||||
const [first] = validator.Errors(value);
|
||||
const path = first?.instancePath.replace(/^\//, "").replace(/\//g, ".") || "root";
|
||||
if (validator.check(value)) return undefined;
|
||||
const [first] = validator.errors(value);
|
||||
const path =
|
||||
first?.path
|
||||
.slice(1)
|
||||
.split("/")
|
||||
.map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"))
|
||||
.join(".") || "root";
|
||||
return `Tool ${tool.name} returned structuredOutput that does not match its schema: ${path}: ${first?.message ?? "invalid"}`;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,15 +8,14 @@ import type {
|
|||
Model,
|
||||
Models,
|
||||
ModelThinkingLevel,
|
||||
Static,
|
||||
Tool,
|
||||
ToolCall,
|
||||
ToolResultMessage,
|
||||
Transport,
|
||||
TSchema,
|
||||
Usage,
|
||||
UserMessage,
|
||||
} from "@earendil-works/pi-ai";
|
||||
import type { Static } from "@earendil-works/pi-schema";
|
||||
import type { TelemetryContext } from "@earendil-works/pi-telemetry";
|
||||
import type { ExecutionEnv, ShellOutputSkip, ShellOutputWindow } from "../env/index.ts";
|
||||
import type {
|
||||
|
|
@ -333,12 +332,22 @@ export interface ToolExecutionApi<TDetails extends JsonValue = JsonValue> extend
|
|||
): Promise<NestedToolExecutionResult>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The arguments type of a tool with parameter schema `TParameters`: `Static<TParameters>` for schemas built with
|
||||
* `@earendil-works/pi-schema`'s builders, `unknown` for other JSON Schema, and `any` for `ToolRegistration<any>`.
|
||||
*/
|
||||
export type ToolArgs<TParameters> = IsAny<TParameters> extends true ? any : Static<TParameters>;
|
||||
|
||||
// `0 extends 1 & T` is resolved too early for a `T` constrained to `object`; this form waits for the argument.
|
||||
type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false;
|
||||
|
||||
/**
|
||||
* Executable tool registered in a registry. Only pi-ai `Tool` fields enter the transcript. `args` are typed by
|
||||
* `parameters`, which the Harness validates them against before `execute()`; `defineTool()` infers both generics.
|
||||
* `parameters` (any JSON Schema object; see {@link ToolArgs}), which the Harness validates them against before
|
||||
* `execute()`; `defineTool()` infers both generics.
|
||||
*/
|
||||
export type ToolRegistration<
|
||||
TParameters extends TSchema = TSchema,
|
||||
TParameters extends object = object,
|
||||
TDetails extends JsonValue = JsonValue,
|
||||
> = Tool<TParameters> & {
|
||||
/** Whether an interrupted execution may rerun on recovery. Default `unsafe`. */
|
||||
|
|
@ -350,12 +359,12 @@ export type ToolRegistration<
|
|||
* pure and must not mutate `args`: it runs again when a call is retried before its intent is recorded. Its result is
|
||||
* still validated against `parameters`.
|
||||
*/
|
||||
prepareArguments?(args: unknown): Static<TParameters>;
|
||||
prepareArguments?(args: unknown): ToolArgs<TParameters>;
|
||||
/**
|
||||
* Schema of `structuredOutput`, which programs that call the tool receive. Omitted: they receive the result's bounded
|
||||
* output, success or error, as `NestedToolExecutionResult.structuredOutput` describes.
|
||||
*/
|
||||
readonly structuredOutputSchema?: TSchema;
|
||||
readonly structuredOutputSchema?: object;
|
||||
/**
|
||||
* Who may call the tool: the model, other tools through `executeTool()`, or both (default). A code mode tool is
|
||||
* `["model"]`, so scripts cannot start scripts; tools behind code mode, such as MCP tools, can be `["tools"]`, so the
|
||||
|
|
@ -373,7 +382,7 @@ export type ToolRegistration<
|
|||
readonly retain?: "head" | "tail";
|
||||
};
|
||||
execute(
|
||||
args: Static<TParameters>,
|
||||
args: ToolArgs<TParameters>,
|
||||
api: ToolExecutionApi<TDetails>,
|
||||
context: Context,
|
||||
): Promise<ToolExecutionResult<TDetails>>;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* and the matches are loaded with `control.addTools`, so the next request offers them. Loading writes the
|
||||
* conversation's `modelTools` (its `add`), so loaded tools stay offered on that branch, also after a compaction.
|
||||
*/
|
||||
import { Type } from "@earendil-works/pi-ai";
|
||||
import { num, obj, opt, str } from "@earendil-works/pi-schema";
|
||||
import { defineExtension, defineTool } from "../harness/define.ts";
|
||||
import type { ToolRegistration } from "../harness/types.ts";
|
||||
import { rankDocuments } from "../search.ts";
|
||||
|
|
@ -14,12 +14,13 @@ export const DEFAULT_TOOL_SEARCH_LIMIT = 8;
|
|||
/** Fixed, so it does not change while deferred tools are registered, for example as MCP servers connect. */
|
||||
const TOOL_SEARCH_DESCRIPTION = `# Tool discovery\n\nSearches over deferred tool metadata with BM25 and exposes matching tools for the next model call.\n\nSome of the tools, such as tools of MCP servers, may not have been provided to you upfront, and you should use this tool (\`${TOOL_SEARCH_TOOL_NAME}\`) to search for the required tools. For MCP tool discovery, always use \`${TOOL_SEARCH_TOOL_NAME}\`.`;
|
||||
|
||||
const toolSearchSchema = Type.Object({
|
||||
query: Type.String({ description: "Search query for deferred tools." }),
|
||||
limit: Type.Optional(
|
||||
Type.Number({ description: `Maximum number of tools to return. Defaults to ${DEFAULT_TOOL_SEARCH_LIMIT}.` }),
|
||||
),
|
||||
});
|
||||
const toolSearchSchema = obj(
|
||||
{
|
||||
query: str({ description: "Search query for deferred tools." }),
|
||||
limit: opt(num({ description: `Maximum number of tools to return. Defaults to ${DEFAULT_TOOL_SEARCH_LIMIT}.` })),
|
||||
},
|
||||
{ open: true },
|
||||
);
|
||||
|
||||
/** `tool_search` result details: the tools it loaded. */
|
||||
export type ToolSearchToolDetails = { loaded: string[] };
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Context } from "@earendil-works/chord";
|
||||
import { type Static, Type } from "typebox";
|
||||
import { bool, num, obj, opt, type Static, str } from "@earendil-works/pi-schema";
|
||||
import { defineTool } from "../harness/define.ts";
|
||||
import type { ToolExecutionApi, ToolExecutionResult, ToolRegistration } from "../harness/types.ts";
|
||||
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES } from "../truncate.ts";
|
||||
|
|
@ -7,23 +7,33 @@ import { requireEnv } from "./env.ts";
|
|||
|
||||
const MAX_TIMEOUT_SECONDS = 2_147_483_647 / 1000;
|
||||
|
||||
const bashSchema = Type.Object({
|
||||
command: Type.String({ description: "Bash command to execute" }),
|
||||
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
|
||||
});
|
||||
// Open objects, as the tools accepted extra arguments when their schemas were TypeBox's.
|
||||
const bashSchema = obj(
|
||||
{
|
||||
command: str({ description: "Bash command to execute" }),
|
||||
timeout: opt(num({ description: "Timeout in seconds (optional, no default timeout)" })),
|
||||
},
|
||||
{ open: true },
|
||||
);
|
||||
|
||||
const powershellSchema = Type.Object({
|
||||
command: Type.String({ description: "PowerShell command to execute" }),
|
||||
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
|
||||
});
|
||||
const powershellSchema = obj(
|
||||
{
|
||||
command: str({ description: "PowerShell command to execute" }),
|
||||
timeout: opt(num({ description: "Timeout in seconds (optional, no default timeout)" })),
|
||||
},
|
||||
{ open: true },
|
||||
);
|
||||
|
||||
/** What a program that calls `bash` or `powershell` receives. */
|
||||
const shellStructuredOutputSchema = Type.Object({
|
||||
output: Type.String({ description: "Combined stdout and stderr: the retained tail, as the model sees it" }),
|
||||
truncated: Type.Boolean({ description: "Whether earlier output was dropped" }),
|
||||
fullOutputPath: Type.Optional(Type.String({ description: "File with the complete output, when it was spilled" })),
|
||||
exitCode: Type.Number(),
|
||||
});
|
||||
const shellStructuredOutputSchema = obj(
|
||||
{
|
||||
output: str({ description: "Combined stdout and stderr: the retained tail, as the model sees it" }),
|
||||
truncated: bool({ description: "Whether earlier output was dropped" }),
|
||||
fullOutputPath: opt(str({ description: "File with the complete output, when it was spilled" })),
|
||||
exitCode: num(),
|
||||
},
|
||||
{ open: true },
|
||||
);
|
||||
|
||||
export type BashToolInput = Static<typeof bashSchema>;
|
||||
export type PowerShellToolInput = Static<typeof powershellSchema>;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { type Static, Type } from "typebox";
|
||||
import { arr, obj, type Static, str } from "@earendil-works/pi-schema";
|
||||
import type { FileError } from "../env/index.ts";
|
||||
import { defineTool } from "../harness/define.ts";
|
||||
import type { ToolRegistration } from "../harness/types.ts";
|
||||
|
|
@ -16,21 +16,27 @@ import { requireEnv } from "./env.ts";
|
|||
import { withFileMutationQueue } from "./file-mutation-queue.ts";
|
||||
import { resolveToolPath } from "./path-utils.ts";
|
||||
|
||||
const replaceEditSchema = Type.Object({
|
||||
oldText: Type.String({
|
||||
description:
|
||||
"Exact text for one targeted replacement. It must be unique in the original file and must not overlap with any other edits[].oldText in the same call.",
|
||||
}),
|
||||
newText: Type.String({ description: "Replacement text for this targeted edit." }),
|
||||
});
|
||||
const replaceEditSchema = obj(
|
||||
{
|
||||
oldText: str({
|
||||
description:
|
||||
"Exact text for one targeted replacement. It must be unique in the original file and must not overlap with any other edits[].oldText in the same call.",
|
||||
}),
|
||||
newText: str({ description: "Replacement text for this targeted edit." }),
|
||||
},
|
||||
{ open: true },
|
||||
);
|
||||
|
||||
const editSchema = Type.Object({
|
||||
path: Type.String({ description: "Path to the file to edit (relative or absolute)" }),
|
||||
edits: Type.Array(replaceEditSchema, {
|
||||
description:
|
||||
"One or more targeted replacements. Each edit is matched against the original file, not incrementally. Do not include overlapping or nested edits. If two changes touch the same block or nearby lines, merge them into one edit instead.",
|
||||
}),
|
||||
});
|
||||
const editSchema = obj(
|
||||
{
|
||||
path: str({ description: "Path to the file to edit (relative or absolute)" }),
|
||||
edits: arr(replaceEditSchema, {
|
||||
description:
|
||||
"One or more targeted replacements. Each edit is matched against the original file, not incrementally. Do not include overlapping or nested edits. If two changes touch the same block or nearby lines, merge them into one edit instead.",
|
||||
}),
|
||||
},
|
||||
{ open: true },
|
||||
);
|
||||
|
||||
export type EditToolInput = Static<typeof editSchema>;
|
||||
type LegacyEditToolInput = EditToolInput & { oldText?: unknown; newText?: unknown };
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { Context } from "@earendil-works/chord";
|
||||
import { characterEnd } from "@earendil-works/chord/output";
|
||||
import { type Static, Type } from "typebox";
|
||||
import { lit, num, obj, opt, type Static, str, union } from "@earendil-works/pi-schema";
|
||||
import {
|
||||
type BinaryReader,
|
||||
type FileInfo,
|
||||
|
|
@ -31,11 +31,14 @@ import {
|
|||
} from "./image-processor.ts";
|
||||
import { resolveReadToolPath } from "./path-utils.ts";
|
||||
|
||||
const readSchema = Type.Object({
|
||||
path: Type.String({ description: "Path to the file to read (relative or absolute)" }),
|
||||
offset: Type.Optional(Type.Number({ description: "Line number to start reading from (1-indexed)" })),
|
||||
limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" })),
|
||||
});
|
||||
const readSchema = obj(
|
||||
{
|
||||
path: str({ description: "Path to the file to read (relative or absolute)" }),
|
||||
offset: opt(num({ description: "Line number to start reading from (1-indexed)" })),
|
||||
limit: opt(num({ description: "Maximum number of lines to read" })),
|
||||
},
|
||||
{ open: true },
|
||||
);
|
||||
|
||||
export type ReadToolInput = Static<typeof readSchema>;
|
||||
|
||||
|
|
@ -43,9 +46,9 @@ export type ReadToolInput = Static<typeof readSchema>;
|
|||
* What programs such as code mode scripts get: the text of a text file, or the image block of an image, which code
|
||||
* mode's `image()` shows.
|
||||
*/
|
||||
const readStructuredOutputSchema = Type.Union([
|
||||
Type.String(),
|
||||
Type.Object({ type: Type.Literal("image"), data: Type.String(), mimeType: Type.String() }),
|
||||
const readStructuredOutputSchema = union([
|
||||
str(),
|
||||
obj({ type: lit("image"), data: str(), mimeType: str() }, { open: true }),
|
||||
]);
|
||||
|
||||
export type ReadToolDetails = {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { type Static, Type } from "typebox";
|
||||
import { obj, type Static, str } from "@earendil-works/pi-schema";
|
||||
import { getOrThrow } from "../env/index.ts";
|
||||
import { defineTool } from "../harness/define.ts";
|
||||
import type { ToolRegistration } from "../harness/types.ts";
|
||||
|
|
@ -6,10 +6,13 @@ import { requireEnv } from "./env.ts";
|
|||
import { withFileMutationQueue } from "./file-mutation-queue.ts";
|
||||
import { resolveToolPath } from "./path-utils.ts";
|
||||
|
||||
const writeSchema = Type.Object({
|
||||
path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
|
||||
content: Type.String({ description: "Content to write to the file" }),
|
||||
});
|
||||
const writeSchema = obj(
|
||||
{
|
||||
path: str({ description: "Path to the file to write (relative or absolute)" }),
|
||||
content: str({ description: "Content to write to the file" }),
|
||||
},
|
||||
{ open: true },
|
||||
);
|
||||
|
||||
export type WriteToolInput = Static<typeof writeSchema>;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
*
|
||||
* node --conditions=source --experimental-strip-types test/codemode-bench.ts [N,...]
|
||||
*/
|
||||
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
|
@ -12,7 +13,7 @@ import { BACKGROUND_CONTEXT as context } from "@earendil-works/chord/context";
|
|||
import { createModels } from "@earendil-works/pi-ai/models";
|
||||
import { fauxAssistantMessage, fauxProvider, fauxText, fauxToolCall } from "@earendil-works/pi-ai/providers/faux";
|
||||
import { CodemodeSandbox } from "@earendil-works/pi-codemode";
|
||||
import { Type } from "typebox";
|
||||
import { num, obj, str } from "@earendil-works/pi-schema";
|
||||
import { createCodemodeTool } from "../src/codemode/codemode.ts";
|
||||
import { createRegistry, defineExtension, defineTool, Harness } from "../src/index.ts";
|
||||
import { openNodeSqliteStorage } from "../src/storage/sqlite/node.ts";
|
||||
|
|
@ -22,14 +23,14 @@ type Shape = "sequential" | "parallel";
|
|||
const echo = defineTool({
|
||||
name: "echo",
|
||||
description: "echo",
|
||||
parameters: Type.Object({ i: Type.Number() }),
|
||||
parameters: obj({ i: num() }),
|
||||
execute: async (args) => ({ output: [{ type: "text", text: String(args.i) }] }),
|
||||
});
|
||||
|
||||
const plain = defineTool({
|
||||
name: "plain",
|
||||
description: "plain",
|
||||
parameters: Type.Object({ n: Type.Number(), shape: Type.String() }),
|
||||
parameters: obj({ n: num(), shape: str() }),
|
||||
replay: "safe",
|
||||
execute: async (args, api, callContext) => {
|
||||
if (args.shape === "sequential") {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { type AssistantMessage, fauxAssistantMessage, fauxText, Type } from "@earendil-works/pi-ai";
|
||||
import { type AssistantMessage, fauxAssistantMessage, fauxText } from "@earendil-works/pi-ai";
|
||||
import { CodemodeSandbox } from "@earendil-works/pi-codemode";
|
||||
import { CODEMODE_SOURCE_GRAMMAR } from "@earendil-works/pi-codemode/source";
|
||||
import { defineTool, MemoryStorage, type ToolRegistration } from "@earendil-works/pi-durable";
|
||||
import { obj, str } from "@earendil-works/pi-schema";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createCodemodeExtension } from "../src/codemode/codemode.ts";
|
||||
import { type CodemodeNamespace, codemodeDescription, codemodeSection } from "../src/codemode/describe.ts";
|
||||
|
|
@ -12,7 +13,7 @@ function tool(name: string, description: string, extra: Partial<ToolRegistration
|
|||
return defineTool({
|
||||
name,
|
||||
description,
|
||||
parameters: Type.Object({ path: Type.String({ description: "File path" }) }),
|
||||
parameters: obj({ path: str({ description: "File path" }) }),
|
||||
execute: async () => ({ output: [] }),
|
||||
...extra,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|||
import { tmpdir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import type { Context } from "@earendil-works/chord";
|
||||
import { type AssistantMessage, fauxAssistantMessage, fauxText, fauxToolCall, Type } from "@earendil-works/pi-ai";
|
||||
import { type AssistantMessage, fauxAssistantMessage, fauxText, fauxToolCall } from "@earendil-works/pi-ai";
|
||||
import { type CodemodeRun, CodemodeSandbox } from "@earendil-works/pi-codemode";
|
||||
import {
|
||||
defineTool,
|
||||
|
|
@ -13,6 +13,7 @@ import {
|
|||
ToolTask,
|
||||
type ToolTaskInput,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { bool, num, obj, opt, str } from "@earendil-works/pi-schema";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
type CodemodeGlobal,
|
||||
|
|
@ -30,7 +31,7 @@ import { addHooks, addTool } from "./harness-support.ts";
|
|||
import { ControlledStorage, context } from "./session-support.ts";
|
||||
import { aborted, type Deferred, deferred, eventually } from "./task-support.ts";
|
||||
|
||||
const Parameters = Type.Object({ text: Type.Optional(Type.String()), id: Type.Optional(Type.Number()) });
|
||||
const Parameters = obj({ text: opt(str()), id: opt(num()) });
|
||||
|
||||
function tool(
|
||||
name: string,
|
||||
|
|
@ -715,13 +716,13 @@ describe("durable code mode", () => {
|
|||
|
||||
it("resolves a schema tool's error result that carries its value, and rejects one without with its text", async () => {
|
||||
const chat = codemodeChat();
|
||||
const Exit = Type.Object({ output: Type.String(), exitCode: Type.Number() });
|
||||
const Exit = obj({ output: str(), exitCode: num() });
|
||||
addTool(
|
||||
chat.setup.registry,
|
||||
defineTool({
|
||||
name: "run",
|
||||
description: "run",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
structuredOutputSchema: Exit,
|
||||
// Like bash on a nonzero exit: an error result with its value.
|
||||
execute: async () => ({
|
||||
|
|
@ -736,7 +737,7 @@ describe("durable code mode", () => {
|
|||
defineTool({
|
||||
name: "denied",
|
||||
description: "denied",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
structuredOutputSchema: Exit,
|
||||
execute: async () => ({ output: [{ type: "text", text: "permission denied" }], isError: true }),
|
||||
}),
|
||||
|
|
@ -767,7 +768,7 @@ describe("durable code mode", () => {
|
|||
defineTool({
|
||||
name: "picture",
|
||||
description: "picture",
|
||||
parameters: Type.Object({ caption: Type.Boolean() }),
|
||||
parameters: obj({ caption: bool() }),
|
||||
execute: async (args) => ({
|
||||
output: [
|
||||
...(args.caption ? [{ type: "text" as const, text: "a cat" }] : []),
|
||||
|
|
@ -1078,7 +1079,7 @@ describe("durable code mode output and tool search", () => {
|
|||
defineTool({
|
||||
name: "later",
|
||||
description: "A deferred tool",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
deferred: true,
|
||||
execute: async () => ({ output: [{ type: "text", text: "later ran" }] }),
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
// Open a Harness with a registry of extensions.
|
||||
// Run from packages/durable:
|
||||
// node --conditions=source --experimental-strip-types test/examples/05-harness.ts
|
||||
|
||||
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
||||
import { Type } from "@earendil-works/pi-ai";
|
||||
import { createModels } from "@earendil-works/pi-ai/models";
|
||||
import { obj, str } from "@earendil-works/pi-schema";
|
||||
import {
|
||||
AgentDoc,
|
||||
createRegistry,
|
||||
|
|
@ -23,7 +24,7 @@ const context = BACKGROUND_CONTEXT;
|
|||
const read = defineTool({
|
||||
name: "read",
|
||||
description: "Read a file",
|
||||
parameters: Type.Object({ path: Type.String() }),
|
||||
parameters: obj({ path: str() }),
|
||||
execute: async (args) => ({ output: [{ type: "text", text: `contents of ${args.path}` }] }),
|
||||
});
|
||||
const Files = defineExtension({ name: "files", tools: [read] });
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
// Per-conversation agent choices and Harness-wide settings.
|
||||
// Run from packages/durable:
|
||||
// node --conditions=source --experimental-strip-types test/examples/06-configuration.ts
|
||||
|
||||
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
||||
import { Type } from "@earendil-works/pi-ai";
|
||||
import { createModels } from "@earendil-works/pi-ai/models";
|
||||
import { obj, str } from "@earendil-works/pi-schema";
|
||||
import {
|
||||
AgentDoc,
|
||||
createRegistry,
|
||||
|
|
@ -25,7 +26,7 @@ function exampleTool(name: string, description: string): AppTool {
|
|||
const tool = defineTool({
|
||||
name,
|
||||
description,
|
||||
parameters: Type.Object({ path: Type.String() }),
|
||||
parameters: obj({ path: str() }),
|
||||
execute: async (args) => ({ output: [{ type: "text", text: `${name} ${args.path}` }] }),
|
||||
});
|
||||
return { ...tool, snippet: `Use ${name} for files.` };
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
// Reload extension code through the registry.
|
||||
// Run from packages/durable:
|
||||
// node --conditions=source --experimental-strip-types test/examples/09-registry-reload.ts
|
||||
|
||||
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
||||
import { Type } from "@earendil-works/pi-ai";
|
||||
import { createModels } from "@earendil-works/pi-ai/models";
|
||||
import { obj, str } from "@earendil-works/pi-schema";
|
||||
import { createRegistry, defineExtension, defineTool, Harness, MemoryStorage, wrapTool } from "../../src/index.ts";
|
||||
|
||||
const context = BACKGROUND_CONTEXT;
|
||||
|
|
@ -12,7 +13,7 @@ function exampleTool(name: string, description: string) {
|
|||
return defineTool({
|
||||
name,
|
||||
description,
|
||||
parameters: Type.Object({ path: Type.String() }),
|
||||
parameters: obj({ path: str() }),
|
||||
execute: async (args) => ({ output: [{ type: "text", text: `${name} ${args.path}` }] }),
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
// An extension that keeps its own per-conversation document.
|
||||
// Run from packages/durable:
|
||||
// node --conditions=source --experimental-strip-types test/examples/10-extension-state.ts
|
||||
|
||||
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
||||
import { Type } from "@earendil-works/pi-ai";
|
||||
import { createModels } from "@earendil-works/pi-ai/models";
|
||||
import { fauxAssistantMessage, fauxProvider, fauxToolCall } from "@earendil-works/pi-ai/providers/faux";
|
||||
import { obj, str } from "@earendil-works/pi-schema";
|
||||
import {
|
||||
createRegistry,
|
||||
defineDoc,
|
||||
|
|
@ -38,7 +39,7 @@ const Todo = defineExtension({
|
|||
defineTool({
|
||||
name: "todo",
|
||||
description: "Add an item to your todo list",
|
||||
parameters: Type.Object({ item: Type.String() }),
|
||||
parameters: obj({ item: str() }),
|
||||
execute: async (args, api, callContext) => {
|
||||
await api.commit(async (tx) => {
|
||||
(await tx.doc(Todos, api.conversationId)).items.push(args.item);
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
// view, and then only what changes after that.
|
||||
// Run from packages/durable:
|
||||
// node --conditions=source --experimental-strip-types test/examples/20-late-join.ts
|
||||
|
||||
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
||||
import { Type } from "@earendil-works/pi-ai";
|
||||
import { createModels } from "@earendil-works/pi-ai/models";
|
||||
import { fauxAssistantMessage, fauxProvider, fauxText, fauxToolCall } from "@earendil-works/pi-ai/providers/faux";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import {
|
||||
createRegistry,
|
||||
defineExtension,
|
||||
|
|
@ -27,7 +28,7 @@ registry.install(
|
|||
defineTool({
|
||||
name: "count",
|
||||
description: "Counts to ten",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api) => {
|
||||
for (let n = 1; n <= 10; n++) {
|
||||
api.output(`${n}\n`);
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@
|
|||
// Uses OpenAI when OPENAI_API_KEY is set, and a scripted faux model otherwise.
|
||||
// Run from packages/durable:
|
||||
// node --conditions=source --experimental-strip-types test/examples/21-subagent-foreground.ts
|
||||
|
||||
import type { Context } from "@earendil-works/chord";
|
||||
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
||||
import { type AssistantMessage, type FauxResponseStep, Type } from "@earendil-works/pi-ai";
|
||||
import type { AssistantMessage, FauxResponseStep } from "@earendil-works/pi-ai";
|
||||
import { createModels } from "@earendil-works/pi-ai/models";
|
||||
import { fauxAssistantMessage, fauxProvider, fauxText, fauxToolCall } from "@earendil-works/pi-ai/providers/faux";
|
||||
import { openaiProvider } from "@earendil-works/pi-ai/providers/openai";
|
||||
import { obj, str } from "@earendil-works/pi-schema";
|
||||
import {
|
||||
AssistantEntry,
|
||||
type ConversationId,
|
||||
|
|
@ -42,7 +44,7 @@ const Subagent: Extension = defineExtension({
|
|||
defineTool({
|
||||
name: "subagent",
|
||||
description: "Delegate a self-contained task to a subagent and get its answer back.",
|
||||
parameters: Type.Object({ task: Type.String({ description: "What the subagent should do" }) }),
|
||||
parameters: obj({ task: str({ description: "What the subagent should do" }) }),
|
||||
// Safe to rerun after a crash: a rerun finds the child it already created and the submission it already made.
|
||||
replay: "safe",
|
||||
execute: async (args, api, callContext) => {
|
||||
|
|
|
|||
|
|
@ -5,14 +5,16 @@
|
|||
// Uses OpenAI when OPENAI_API_KEY is set, and a scripted faux model otherwise.
|
||||
// Run from packages/durable:
|
||||
// node --conditions=source --experimental-strip-types test/examples/22-subagent-background.ts
|
||||
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
||||
import { type AssistantMessage, type FauxResponseStep, Type } from "@earendil-works/pi-ai";
|
||||
import type { AssistantMessage, FauxResponseStep } from "@earendil-works/pi-ai";
|
||||
import { createModels } from "@earendil-works/pi-ai/models";
|
||||
import { fauxAssistantMessage, fauxProvider, fauxText, fauxToolCall } from "@earendil-works/pi-ai/providers/faux";
|
||||
import { openaiProvider } from "@earendil-works/pi-ai/providers/openai";
|
||||
import { bool, obj, oneOf, opt, str } from "@earendil-works/pi-schema";
|
||||
import {
|
||||
AssistantEntry,
|
||||
type ConversationId,
|
||||
|
|
@ -134,11 +136,11 @@ const subagentTool = defineTool({
|
|||
"Manage persistent subagents that work in the background. Actions: spawn (name, message), send (name, message; " +
|
||||
"followUp: true queues it after the current answer instead of steering), stop (name: aborts its current work), " +
|
||||
"status (name, or all subagents without one). Answers are reported back to you when they arrive.",
|
||||
parameters: Type.Object({
|
||||
action: Type.Union([Type.Literal("spawn"), Type.Literal("send"), Type.Literal("stop"), Type.Literal("status")]),
|
||||
name: Type.Optional(Type.String()),
|
||||
message: Type.Optional(Type.String()),
|
||||
followUp: Type.Optional(Type.Boolean()),
|
||||
parameters: obj({
|
||||
action: oneOf(["spawn", "send", "stop", "status"]),
|
||||
name: opt(str()),
|
||||
message: opt(str()),
|
||||
followUp: opt(bool()),
|
||||
}),
|
||||
// A call interrupted by a crash is not rerun: repeating `stop` could stop newer work. The model sees that the call
|
||||
// was interrupted and can check with `status`.
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@
|
|||
// extension's own document, and switching back restores the full tool set.
|
||||
// Run from packages/durable:
|
||||
// node --conditions=source --experimental-strip-types test/examples/26-plan-mode.ts
|
||||
|
||||
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
||||
import { Type } from "@earendil-works/pi-ai";
|
||||
import { createModels } from "@earendil-works/pi-ai/models";
|
||||
import { fauxAssistantMessage, fauxProvider, fauxToolCall } from "@earendil-works/pi-ai/providers/faux";
|
||||
import { arr, obj, str } from "@earendil-works/pi-schema";
|
||||
import { NodeExecutionEnv } from "../../src/env/node.ts";
|
||||
import {
|
||||
createRegistry,
|
||||
|
|
@ -39,7 +40,7 @@ const PlanDoc = defineDoc<{ steps: string[] }>({
|
|||
const submitPlan = defineTool({
|
||||
name: "submit_plan",
|
||||
description: "Submit the plan as a list of steps.",
|
||||
parameters: Type.Object({ steps: Type.Array(Type.String()) }),
|
||||
parameters: obj({ steps: arr(str()) }),
|
||||
execute: async (args, api, callContext) => {
|
||||
await api.commit(async (tx) => {
|
||||
(await tx.doc(PlanDoc, api.conversationId)).steps = args.steps;
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@
|
|||
// with; stored choices are names, so they survive a restart and bind to whatever code the new process installs.
|
||||
// Run from packages/durable:
|
||||
// node --conditions=source --experimental-strip-types test/examples/30-reload-and-restart.ts
|
||||
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
||||
import { type ToolResultMessage, Type } from "@earendil-works/pi-ai";
|
||||
import type { ToolResultMessage } from "@earendil-works/pi-ai";
|
||||
import { createModels } from "@earendil-works/pi-ai/models";
|
||||
import { fauxAssistantMessage, fauxProvider, fauxToolCall } from "@earendil-works/pi-ai/providers/faux";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { createRegistry, defineExtension, defineTool, Harness, ToolResultEntry } from "../../src/index.ts";
|
||||
import { openNodeSqliteStorage } from "../../src/storage/sqlite/node.ts";
|
||||
|
||||
|
|
@ -24,7 +26,7 @@ function loadVersioned(version: string) {
|
|||
defineTool({
|
||||
name: "version",
|
||||
description: "Report the tool's code version",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => {
|
||||
started();
|
||||
await gate;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
// tests, a failure starts a fix run, and input sent meanwhile waits for them too.
|
||||
// Run from packages/durable:
|
||||
// node --conditions=source --experimental-strip-types test/examples/32-tests-after-edits.ts
|
||||
|
||||
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
||||
import { Type } from "@earendil-works/pi-ai";
|
||||
import { createModels } from "@earendil-works/pi-ai/models";
|
||||
import { fauxAssistantMessage, fauxProvider, fauxToolCall } from "@earendil-works/pi-ai/providers/faux";
|
||||
import { obj, str } from "@earendil-works/pi-schema";
|
||||
import {
|
||||
createRegistry,
|
||||
defineExtension,
|
||||
|
|
@ -34,7 +35,7 @@ const runTests = () => {
|
|||
const edit = defineTool({
|
||||
name: "edit",
|
||||
description: "Replace a file's content",
|
||||
parameters: Type.Object({ path: Type.String(), content: Type.String() }),
|
||||
parameters: obj({ path: str(), content: str() }),
|
||||
execute: async (args) => {
|
||||
files.set(args.path, args.content);
|
||||
return { output: [{ type: "text", text: `wrote ${args.path}` }] };
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
// message, with a fresh budget. A host learns that a run stopped from `pi.live.runFailure`.
|
||||
// Run from packages/durable:
|
||||
// node --conditions=source --experimental-strip-types test/examples/33-turn-limit-and-continue.ts
|
||||
|
||||
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
||||
import { Type } from "@earendil-works/pi-ai";
|
||||
import { createModels } from "@earendil-works/pi-ai/models";
|
||||
import { fauxAssistantMessage, fauxProvider, fauxToolCall } from "@earendil-works/pi-ai/providers/faux";
|
||||
import { num, obj } from "@earendil-works/pi-schema";
|
||||
import {
|
||||
ConversationBusy,
|
||||
createRegistry,
|
||||
|
|
@ -21,7 +22,7 @@ const context = BACKGROUND_CONTEXT;
|
|||
const step = defineTool({
|
||||
name: "step",
|
||||
description: "Do one step of the work",
|
||||
parameters: Type.Object({ n: Type.Number() }),
|
||||
parameters: obj({ n: num() }),
|
||||
execute: async (args) => ({ output: [{ type: "text", text: `step ${args.n} done` }] }),
|
||||
});
|
||||
const call = (n: number) => fauxAssistantMessage(fauxToolCall("step", { n }), { stopReason: "toolUse" });
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@
|
|||
// state; `whenIdle: "reject"` refuses it when no run is working.
|
||||
// Run from packages/durable:
|
||||
// node --conditions=source --experimental-strip-types test/examples/34-submit-in-commit.ts
|
||||
|
||||
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
||||
import { Type } from "@earendil-works/pi-ai";
|
||||
import { createModels } from "@earendil-works/pi-ai/models";
|
||||
import { fauxAssistantMessage, fauxProvider, fauxToolCall } from "@earendil-works/pi-ai/providers/faux";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import {
|
||||
ConversationIdle,
|
||||
createRegistry,
|
||||
|
|
@ -38,7 +39,7 @@ const working = new Promise<void>((resolve) => {
|
|||
const build = defineTool({
|
||||
name: "build",
|
||||
description: "Build the project",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => {
|
||||
await working;
|
||||
return { output: [{ type: "text", text: "built" }] };
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { mkdtemp, rm } from "node:fs/promises";
|
|||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import type { Draft, JsonValue } from "@earendil-works/chord";
|
||||
import { fauxAssistantMessage, fauxToolCall, Type } from "@earendil-works/pi-ai";
|
||||
import { fauxAssistantMessage, fauxToolCall } from "@earendil-works/pi-ai";
|
||||
import {
|
||||
CompactionTask,
|
||||
ConversationBusy,
|
||||
|
|
@ -25,6 +25,7 @@ import {
|
|||
type TaskId,
|
||||
type Tx,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { endRun } from "../src/harness/live.ts";
|
||||
import { openNodeSqliteStorage } from "../src/storage/sqlite/node.ts";
|
||||
|
|
@ -132,7 +133,7 @@ function install(setup: ChatSetup, state: Probe, afterRun?: GenerationHooks["aft
|
|||
return Tests;
|
||||
}
|
||||
|
||||
const Parameters = Type.Object({});
|
||||
const Parameters = obj({});
|
||||
const tools = defineExtension({
|
||||
name: "tools",
|
||||
tools: [
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { type AssistantMessage, fauxAssistantMessage, fauxText, fauxToolCall, Type } from "@earendil-works/pi-ai";
|
||||
import { type AssistantMessage, fauxAssistantMessage, fauxText, fauxToolCall } from "@earendil-works/pi-ai";
|
||||
import {
|
||||
AgentDoc,
|
||||
type Conversation,
|
||||
|
|
@ -7,6 +7,7 @@ import {
|
|||
type NestedToolExecutionResult,
|
||||
type ToolRegistration,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { type ChatSetup, chatSetup, openChat } from "./chat-support.ts";
|
||||
import { addTool } from "./harness-support.ts";
|
||||
|
|
@ -16,7 +17,7 @@ function tool(name: string, extra: Partial<ToolRegistration> = {}): ToolRegistra
|
|||
return defineTool({
|
||||
name,
|
||||
description: name,
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => ({ output: [{ type: "text", text: `${name} ran` }] }),
|
||||
...extra,
|
||||
});
|
||||
|
|
@ -34,7 +35,7 @@ function prober(names: readonly string[]) {
|
|||
const registration = defineTool({
|
||||
name: "probe",
|
||||
description: "probe",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api, callContext) => {
|
||||
for (const name of names) received.set(name, await api.executeTool(name, {}, callContext));
|
||||
return {};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
type Message,
|
||||
type SimpleStreamOptions,
|
||||
type TranscriptContext,
|
||||
Type,
|
||||
type UserMessage,
|
||||
} from "@earendil-works/pi-ai";
|
||||
import {
|
||||
|
|
@ -33,6 +32,7 @@ import {
|
|||
type TaskId,
|
||||
UsageDoc,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { selectCut, serializeConversation } from "../src/harness/compaction.ts";
|
||||
import { orderToolResults } from "../src/harness/context.ts";
|
||||
|
|
@ -471,7 +471,7 @@ describe("manual compaction", () => {
|
|||
defineTool({
|
||||
name: "wait",
|
||||
description: "wait",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => ({ output: [{ type: "text", text: "waited" }] }),
|
||||
}),
|
||||
);
|
||||
|
|
@ -886,7 +886,7 @@ describe("compaction outcomes", () => {
|
|||
defineTool({
|
||||
name: "read",
|
||||
description: "read",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => ({ output: [] }),
|
||||
}),
|
||||
);
|
||||
|
|
@ -1343,7 +1343,7 @@ describe("compaction estimates and interactions", () => {
|
|||
defineTool({
|
||||
name: "slow",
|
||||
description: "slow",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => {
|
||||
toolReached.resolve();
|
||||
await toolGate.promise;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// `submit({ type: "continue" })`: a run without a new user message (#11043).
|
||||
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
|
@ -10,7 +11,6 @@ import {
|
|||
fauxText,
|
||||
fauxToolCall,
|
||||
type Message,
|
||||
Type,
|
||||
} from "@earendil-works/pi-ai";
|
||||
import {
|
||||
AssistantEntry,
|
||||
|
|
@ -26,6 +26,7 @@ import {
|
|||
type Storage,
|
||||
UserEntry,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { InMemoryTelemetryContext } from "@earendil-works/pi-telemetry";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { openNodeSqliteStorage } from "../src/storage/sqlite/node.ts";
|
||||
|
|
@ -125,7 +126,7 @@ describe("continue", () => {
|
|||
defineTool({
|
||||
name: "wait",
|
||||
description: "Waits until aborted",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, _api, callContext) => {
|
||||
started.resolve();
|
||||
return aborted(callContext.abortSignal!);
|
||||
|
|
@ -312,7 +313,7 @@ describe("continue", () => {
|
|||
defineTool({
|
||||
name: "delegate",
|
||||
description: "Delegates",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api, callContext) => {
|
||||
const child = await api.commit(async (tx) => {
|
||||
const created = await tx.createConversation({ ownership: { kind: "task", taskId: api.taskId } });
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
// the next commit that reads or writes `pi.live` or within `settings.progress.persistWithinMs`. Crashes are simulated
|
||||
// by failing the Harness: from that moment nothing more reaches Storage, as when the process dies; the reopened Harness
|
||||
// resumes from what was stored.
|
||||
|
||||
import { applyImmutable } from "@earendil-works/chord/delta";
|
||||
import { fauxAssistantMessage, fauxText, fauxToolCall, Type } from "@earendil-works/pi-ai";
|
||||
import { fauxAssistantMessage, fauxText, fauxToolCall } from "@earendil-works/pi-ai";
|
||||
import {
|
||||
AssistantEntry,
|
||||
type ConversationId,
|
||||
|
|
@ -15,6 +16,7 @@ import {
|
|||
type LiveState,
|
||||
ToolResultEntry,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { PartialMessage } from "../src/harness/partial-changes.ts";
|
||||
import type { SessionImpl } from "../src/session/session.ts";
|
||||
|
|
@ -257,7 +259,7 @@ describe("tool output", () => {
|
|||
const registration = defineTool({
|
||||
name: "work",
|
||||
description: "work",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
replay,
|
||||
execute: async (_args, api, callContext) => {
|
||||
const run = ++state.runs;
|
||||
|
|
@ -306,7 +308,7 @@ describe("tool output", () => {
|
|||
defineTool({
|
||||
name: "work",
|
||||
description: "work",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
replay: "safe",
|
||||
execute: async (_args, api, callContext) => {
|
||||
runs++;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import {
|
|||
fauxToolCall,
|
||||
type Models,
|
||||
type SimpleStreamOptions,
|
||||
Type,
|
||||
} from "@earendil-works/pi-ai";
|
||||
import {
|
||||
type Conversation,
|
||||
|
|
@ -19,6 +18,7 @@ import {
|
|||
MemoryStorage,
|
||||
type Registry,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { allEntries, textOf } from "./chat-support.ts";
|
||||
import { context } from "./session-support.ts";
|
||||
|
|
@ -86,7 +86,7 @@ describe("HarnessOptions.models and settings as functions", () => {
|
|||
defineTool({
|
||||
name: "probe",
|
||||
description: "Records api.models before and after the host swaps it",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api) => {
|
||||
seen.push(api.models);
|
||||
current = second;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
fauxText,
|
||||
fauxToolCall,
|
||||
type ToolResultMessage,
|
||||
Type,
|
||||
type Usage,
|
||||
} from "@earendil-works/pi-ai";
|
||||
import {
|
||||
|
|
@ -29,6 +28,7 @@ import {
|
|||
ToolTask,
|
||||
UsageDoc,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { recordUsage } from "../src/harness/usage.ts";
|
||||
import { openNodeSqliteStorage } from "../src/storage/sqlite/node.ts";
|
||||
|
|
@ -67,7 +67,7 @@ function holdTool(setup: ChatSetup, gate: Deferred, result: ToolExecutionResult
|
|||
defineTool({
|
||||
name: "hold",
|
||||
description: "Waits for the test",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => {
|
||||
await gate.promise;
|
||||
return result;
|
||||
|
|
@ -474,7 +474,7 @@ describe("inbox", () => {
|
|||
defineTool({
|
||||
name: "hold",
|
||||
description: "Hands off",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => ({ output: [], control: { handoff: `h${++handoffs}` } }),
|
||||
}),
|
||||
);
|
||||
|
|
@ -796,7 +796,7 @@ describe("inbox", () => {
|
|||
defineTool({
|
||||
name: "later",
|
||||
description: "Finishes first",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => {
|
||||
await secondGate.promise;
|
||||
return { output: [], control: { handoff: "two" } };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Inputs placed as application entries, and data-only writes appended at once (#10407).
|
||||
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
|
@ -9,7 +10,6 @@ import {
|
|||
fauxText,
|
||||
fauxToolCall,
|
||||
type Context as ModelContext,
|
||||
Type,
|
||||
} from "@earendil-works/pi-ai";
|
||||
import {
|
||||
type Conversation,
|
||||
|
|
@ -23,6 +23,7 @@ import {
|
|||
MemoryStorage,
|
||||
type SubmissionDraft,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { openNodeSqliteStorage } from "../src/storage/sqlite/node.ts";
|
||||
import { allEntries, type ChatSetup, chatSetup, openChat, textOf, waitFor } from "./chat-support.ts";
|
||||
|
|
@ -54,7 +55,7 @@ function holdTool(setup: ChatSetup, gate: Deferred): void {
|
|||
defineTool({
|
||||
name: "hold",
|
||||
description: "Waits for the test",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => {
|
||||
await gate.promise;
|
||||
return { output: [{ type: "text", text: "held" }] };
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { tmpdir } from "node:os";
|
|||
import { join } from "node:path";
|
||||
import type { Context, JsonValue } from "@earendil-works/chord";
|
||||
import { withAbortSignal } from "@earendil-works/chord/context";
|
||||
import { createModels, fauxAssistantMessage, fauxText, fauxToolCall, Type } from "@earendil-works/pi-ai";
|
||||
import { createModels, fauxAssistantMessage, fauxText, fauxToolCall } from "@earendil-works/pi-ai";
|
||||
import {
|
||||
type Conversation,
|
||||
type ConversationId,
|
||||
|
|
@ -17,6 +17,7 @@ import {
|
|||
type Storage,
|
||||
type TaskId,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { SessionImpl } from "../src/session/session.ts";
|
||||
import { openNodeSqliteStorage } from "../src/storage/sqlite/node.ts";
|
||||
|
|
@ -168,7 +169,7 @@ describe("Harness close", () => {
|
|||
defineTool({
|
||||
name: "wait",
|
||||
description: "wait",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => {
|
||||
if (where === "tool") await stubborn();
|
||||
return { output: [] };
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Op } from "@earendil-works/chord/delta";
|
||||
import { fauxAssistantMessage, fauxText, fauxToolCall, Type } from "@earendil-works/pi-ai";
|
||||
import { fauxAssistantMessage, fauxText, fauxToolCall } from "@earendil-works/pi-ai";
|
||||
import {
|
||||
type CommitPublication,
|
||||
defineTool,
|
||||
|
|
@ -10,6 +10,7 @@ import {
|
|||
type ToolExecutionApi,
|
||||
type ToolRegistration,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { chatSetup, openChat, waitFor } from "./chat-support.ts";
|
||||
import { addTool } from "./harness-support.ts";
|
||||
|
|
@ -30,7 +31,7 @@ async function drive(outputLimits: ToolRegistration["outputLimits"] = {}) {
|
|||
defineTool({
|
||||
name: "drive",
|
||||
description: "Driven by the test",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
outputLimits,
|
||||
execute: async (_args, api, callContext) => {
|
||||
for (;;) {
|
||||
|
|
@ -104,7 +105,7 @@ describe("pi.live deltas", () => {
|
|||
defineTool({
|
||||
name: "noop",
|
||||
description: "noop",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => ({ output: [] }),
|
||||
}),
|
||||
);
|
||||
|
|
@ -317,7 +318,7 @@ describe("pi.live deltas", () => {
|
|||
defineTool({
|
||||
name,
|
||||
description: name,
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api) => {
|
||||
api.output(`${name} output\n`);
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
|
|
@ -368,7 +369,7 @@ describe("pi.live deltas", () => {
|
|||
defineTool({
|
||||
name: "bad",
|
||||
description: "bad",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
// Not strict JSON: the result commit throws and the scheduler faults the task.
|
||||
execute: async () => ({ output: [], details: { fn: (() => 1) as never } }),
|
||||
}),
|
||||
|
|
@ -412,7 +413,7 @@ describe("pi.live deltas", () => {
|
|||
defineTool({
|
||||
name: "noop",
|
||||
description: "noop",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => ({ output: [] }),
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {
|
|||
fauxText,
|
||||
fauxToolCall,
|
||||
type ToolResultMessage,
|
||||
Type,
|
||||
} from "@earendil-works/pi-ai";
|
||||
import {
|
||||
defineExtension,
|
||||
|
|
@ -23,6 +22,7 @@ import {
|
|||
ToolTask,
|
||||
type ToolTaskInput,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj, opt, str } from "@earendil-works/pi-schema";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { openNodeSqliteStorage } from "../src/storage/sqlite/node.ts";
|
||||
import { allEntries, type ChatSetup, chatSetup, openChat } from "./chat-support.ts";
|
||||
|
|
@ -31,7 +31,7 @@ import { nestedResultOf, nestedResults, viewHistory } from "./live-support.ts";
|
|||
import { context } from "./session-support.ts";
|
||||
import { aborted, deferred, eventually } from "./task-support.ts";
|
||||
|
||||
const Parameters = Type.Object({ text: Type.Optional(Type.String()) });
|
||||
const Parameters = obj({ text: opt(str()) });
|
||||
|
||||
function tool(
|
||||
name: string,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import {
|
|||
fauxText,
|
||||
fauxToolCall,
|
||||
type ToolResultMessage,
|
||||
Type,
|
||||
type Usage,
|
||||
} from "@earendil-works/pi-ai";
|
||||
import {
|
||||
|
|
@ -33,6 +32,7 @@ import {
|
|||
type ToolTaskResult,
|
||||
UsageDoc,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj, opt, str } from "@earendil-works/pi-schema";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { openNodeSqliteStorage } from "../src/storage/sqlite/node.ts";
|
||||
import { commitWrites } from "../src/testing/commit-writes.ts";
|
||||
|
|
@ -42,7 +42,7 @@ import { endedAt, nestedResultOf, nestedResults, slotsOf, startOrder, viewHistor
|
|||
import { context, onValue } from "./session-support.ts";
|
||||
import { aborted, deferred, eventually } from "./task-support.ts";
|
||||
|
||||
const Parameters = Type.Object({ text: Type.Optional(Type.String()) });
|
||||
const Parameters = obj({ text: opt(str()) });
|
||||
type Tool = ToolRegistration<typeof Parameters>;
|
||||
|
||||
function tool(name: string, execute: Tool["execute"], extra: Partial<Tool> = {}): Tool {
|
||||
|
|
@ -329,7 +329,7 @@ describe("nested tool calls", () => {
|
|||
defineTool({
|
||||
name: "pair",
|
||||
description: "pair",
|
||||
parameters: Type.Object({ a: Type.String(), b: Type.String() }),
|
||||
parameters: obj({ a: str(), b: str() }),
|
||||
execute: async (args) => {
|
||||
runs++;
|
||||
return { output: [{ type: "text", text: `${args.a}${args.b}` }] };
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { type AssistantMessage, fauxAssistantMessage, fauxText, fauxToolCall, Type } from "@earendil-works/pi-ai";
|
||||
import { type AssistantMessage, fauxAssistantMessage, fauxText, fauxToolCall } from "@earendil-works/pi-ai";
|
||||
import {
|
||||
type Conversation,
|
||||
type ConversationHandle,
|
||||
|
|
@ -20,6 +20,7 @@ import {
|
|||
type Submission,
|
||||
type TaskId,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj, str } from "@earendil-works/pi-schema";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { openNodeSqliteStorage } from "../src/storage/sqlite/node.ts";
|
||||
import { chatSetup, openChat } from "./chat-support.ts";
|
||||
|
|
@ -1019,7 +1020,7 @@ describe("owned conversations from tools and supervisors", () => {
|
|||
defineTool({
|
||||
name: "delegate",
|
||||
description: "Delegates",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api, callContext) => {
|
||||
const child = await api.commit(async (tx) => {
|
||||
const created = await tx.createConversation({ ownership: { kind: "task", taskId: api.taskId } });
|
||||
|
|
@ -1074,7 +1075,7 @@ describe("owned conversations from tools and supervisors", () => {
|
|||
defineTool({
|
||||
name: "delegate",
|
||||
description: "Delegates late",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api, callContext) => {
|
||||
const child = await api.commit(async (tx) => {
|
||||
const created = await tx.createConversation({ ownership: { kind: "task", taskId: api.taskId } });
|
||||
|
|
@ -1159,7 +1160,7 @@ describe("owned conversations from tools and supervisors", () => {
|
|||
defineTool({
|
||||
name: "delegate",
|
||||
description: "Delegates",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api, callContext) => {
|
||||
child = await api.commit(async (tx) => {
|
||||
const created = await tx.createConversation({ ownership: { kind: "task", taskId: api.taskId } });
|
||||
|
|
@ -1208,7 +1209,7 @@ describe("owned conversations from tools and supervisors", () => {
|
|||
defineTool({
|
||||
name: "delegate",
|
||||
description: "Delegates and cancels",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api, callContext) => {
|
||||
const child = await api.commit(async (tx) => {
|
||||
const created = await tx.createConversation({ ownership: { kind: "task", taskId: api.taskId } });
|
||||
|
|
@ -1247,7 +1248,7 @@ describe("owned conversations from tools and supervisors", () => {
|
|||
defineTool({
|
||||
name: "spawn",
|
||||
description: "Starts owned work, then throws or hangs",
|
||||
parameters: Type.Object({ mode: Type.String() }),
|
||||
parameters: obj({ mode: str() }),
|
||||
execute: async (args, api, callContext) => {
|
||||
const child = await api.commit(async (tx) => {
|
||||
const created = await tx.createConversation({ ownership: { kind: "task", taskId: api.taskId } });
|
||||
|
|
@ -1305,7 +1306,7 @@ describe("owned conversations from tools and supervisors", () => {
|
|||
defineTool({
|
||||
name: "subagent",
|
||||
description: "Delegates",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
replay: "safe",
|
||||
execute: async (_args, api, callContext) => {
|
||||
const child = await api.commit(async (tx) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { type SystemMessage, type Tool, Type } from "@earendil-works/pi-ai";
|
||||
import type { SystemMessage, Tool } from "@earendil-works/pi-ai";
|
||||
import { getCurrentTools, toToolDeclaration } from "@earendil-works/pi-ai/utils/transcript";
|
||||
import {
|
||||
type Conversation,
|
||||
|
|
@ -12,6 +12,7 @@ import {
|
|||
type ToolRegistration,
|
||||
wrapSection,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveAgent, resolveSettings } from "../src/harness/agent.ts";
|
||||
import { planSystemEntries, renderSections, replaySections } from "../src/harness/prompt.ts";
|
||||
|
|
@ -207,7 +208,7 @@ describe("tool loadout preparation", () => {
|
|||
const declaration = (name: string, description = name): Tool => ({
|
||||
name,
|
||||
description,
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
});
|
||||
|
||||
/** Plan tools only, append the plan, check that replay offers `tools` in order, and return each message's changes. */
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { Type } from "@earendil-works/pi-ai";
|
||||
import {
|
||||
type AgentState,
|
||||
CompactionTask,
|
||||
|
|
@ -15,6 +14,7 @@ import {
|
|||
wrapSection,
|
||||
wrapTool,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { agentHooks, resolveAgent, resolveSettings } from "../src/harness/agent.ts";
|
||||
import { context } from "./session-support.ts";
|
||||
|
|
@ -25,7 +25,7 @@ function tool(name: string, extra: Partial<AppTool> = {}): AppTool {
|
|||
return {
|
||||
name,
|
||||
description: `${name} tool`,
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => ({ output: [] }),
|
||||
...extra,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { createModels, fauxAssistantMessage, Type } from "@earendil-works/pi-ai";
|
||||
import { createModels, fauxAssistantMessage } from "@earendil-works/pi-ai";
|
||||
import {
|
||||
type AnyTask,
|
||||
createRegistry,
|
||||
|
|
@ -14,6 +14,7 @@ import {
|
|||
section,
|
||||
wrapTool,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { chatSetup } from "./chat-support.ts";
|
||||
import { ControlledStorage, context } from "./session-support.ts";
|
||||
|
|
@ -189,7 +190,7 @@ describe("reports", () => {
|
|||
const echo = defineTool({
|
||||
name: "echo",
|
||||
description: "echo",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => ({}),
|
||||
});
|
||||
registry.install(defineExtension({ name: "tools", tools: [echo] }));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// What a crash (a host that dies, is evicted, or is reset) does to work that was running: a generation's interrupted
|
||||
// request is a failed attempt under the retry policy, and every other task ends visibly once it was cut off
|
||||
// `settings.restartLimit` times in a row (#11139).
|
||||
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
|
@ -13,7 +14,6 @@ import {
|
|||
type Message,
|
||||
type Models,
|
||||
type ToolResultMessage,
|
||||
Type,
|
||||
} from "@earendil-works/pi-ai";
|
||||
import {
|
||||
defineTool,
|
||||
|
|
@ -27,6 +27,7 @@ import {
|
|||
type SubmissionId,
|
||||
type TaskId,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { openNodeSqliteStorage } from "../src/storage/sqlite/node.ts";
|
||||
import { commitWrites } from "../src/testing/commit-writes.ts";
|
||||
|
|
@ -282,7 +283,7 @@ describe("a generation interrupted while the model answers (#11139)", () => {
|
|||
defineTool({
|
||||
name: "hold",
|
||||
description: "Waits",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
replay: "safe",
|
||||
execute: async () => {
|
||||
started.resolve();
|
||||
|
|
@ -375,7 +376,7 @@ describe("other tasks cut off restartLimit times in a row (#11139)", () => {
|
|||
defineTool({
|
||||
name: "heavy",
|
||||
description: "Never finishes",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
replay: "safe",
|
||||
execute: async (_args, api, ctx) => {
|
||||
runs++;
|
||||
|
|
@ -751,7 +752,7 @@ describe("the restart limit on the normal path (#11139)", () => {
|
|||
defineTool({
|
||||
name: "t",
|
||||
description: "t",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
replay: "safe",
|
||||
execute: async () => ({}),
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// `pi.live.runFailure`: why the newest run ended without an answer, as state a renderer shows (#11327).
|
||||
import { type FauxResponseStep, fauxAssistantMessage, fauxText, fauxToolCall, Type } from "@earendil-works/pi-ai";
|
||||
|
||||
import { type FauxResponseStep, fauxAssistantMessage, fauxText, fauxToolCall } from "@earendil-works/pi-ai";
|
||||
import {
|
||||
type AfterFailedRun,
|
||||
type Conversation,
|
||||
|
|
@ -10,6 +11,7 @@ import {
|
|||
MemoryStorage,
|
||||
type RunFailure,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { chatSetup, openChat, unanswered } from "./chat-support.ts";
|
||||
import { addTool } from "./harness-support.ts";
|
||||
|
|
@ -104,7 +106,7 @@ describe("pi.live.runFailure", () => {
|
|||
defineTool({
|
||||
name: "hold",
|
||||
description: "Waits",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => {
|
||||
started.resolve();
|
||||
await gate.promise;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {
|
|||
fauxText,
|
||||
fauxToolCall,
|
||||
type ToolResultMessage,
|
||||
Type,
|
||||
} from "@earendil-works/pi-ai";
|
||||
import {
|
||||
defineTool,
|
||||
|
|
@ -21,6 +20,7 @@ import {
|
|||
ToolResultEntry,
|
||||
ToolTask,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { nul, num, obj } from "@earendil-works/pi-schema";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { NodeExecutionEnv } from "../src/env/node.ts";
|
||||
import { createBashTool } from "../src/tools/index.ts";
|
||||
|
|
@ -28,14 +28,14 @@ import { allEntries, type ChatSetup, chatSetup, openChat } from "./chat-support.
|
|||
import { addHooks, addTool } from "./harness-support.ts";
|
||||
import { context } from "./session-support.ts";
|
||||
|
||||
const Count = Type.Object({ count: Type.Number() });
|
||||
const Count = obj({ count: num() });
|
||||
|
||||
/** A tool that returns `result`, with `extra` such as a schema. */
|
||||
function returning(name: string, result: ToolExecutionResult, extra: Partial<ToolRegistration> = {}): ToolRegistration {
|
||||
return defineTool({
|
||||
name,
|
||||
description: name,
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => result,
|
||||
...extra,
|
||||
});
|
||||
|
|
@ -55,7 +55,7 @@ async function nested(setup: ChatSetup, name: string): Promise<NestedToolExecuti
|
|||
defineTool({
|
||||
name: "probe",
|
||||
description: "probe",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api, callContext) => {
|
||||
received = await api.executeTool(name, {}, callContext);
|
||||
return {};
|
||||
|
|
@ -132,17 +132,14 @@ describe("structured output", () => {
|
|||
it("stores exactly what callers and events get, without the model's output, and keeps a stored null", async () => {
|
||||
const setup = chatSetup();
|
||||
addTool(setup.registry, returning("plain", { output: [{ type: "text", text: "a" }] }));
|
||||
addTool(
|
||||
setup.registry,
|
||||
returning("nothing", { structuredOutput: null }, { structuredOutputSchema: Type.Null() }),
|
||||
);
|
||||
addTool(setup.registry, returning("nothing", { structuredOutput: null }, { structuredOutputSchema: nul() }));
|
||||
const received: NestedToolExecutionResult[] = [];
|
||||
addTool(
|
||||
setup.registry,
|
||||
defineTool({
|
||||
name: "probe",
|
||||
description: "probe",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api, callContext) => {
|
||||
received.push(await api.executeTool("plain", {}, callContext));
|
||||
received.push(await api.executeTool("nothing", {}, callContext));
|
||||
|
|
@ -272,7 +269,7 @@ describe("structured output", () => {
|
|||
defineTool({
|
||||
name: "throws",
|
||||
description: "throws",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
structuredOutputSchema: Count,
|
||||
execute: async () => {
|
||||
throw new Error("permission denied");
|
||||
|
|
@ -344,7 +341,7 @@ describe("structured output", () => {
|
|||
defineTool({
|
||||
name: "probe",
|
||||
description: "probe",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api, callContext) => {
|
||||
plain = await api.executeTool("plain", {}, callContext);
|
||||
counter = await api.executeTool("counter", {}, callContext);
|
||||
|
|
@ -375,7 +372,7 @@ describe("structured output", () => {
|
|||
defineTool({
|
||||
name: "probe",
|
||||
description: "probe",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api, callContext) => {
|
||||
received.push(await api.executeTool("bash", { command: "printf ok" }, callContext));
|
||||
received.push(await api.executeTool("bash", { command: "printf bad; exit 4" }, callContext));
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {
|
|||
fauxToolCall,
|
||||
type Models,
|
||||
type ToolResultMessage,
|
||||
Type,
|
||||
} from "@earendil-works/pi-ai";
|
||||
import {
|
||||
type Conversation,
|
||||
|
|
@ -35,6 +34,7 @@ import {
|
|||
ToolResultEntry,
|
||||
type Tx,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { openNodeSqliteStorage } from "../src/storage/sqlite/node.ts";
|
||||
import { allEntries, chatSetup, openChat, waitFor } from "./chat-support.ts";
|
||||
|
|
@ -1192,7 +1192,7 @@ function blockingTool(name: string): { started: Promise<void>; registration: Too
|
|||
registration: defineTool({
|
||||
name,
|
||||
description: `The ${name} tool`,
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
executionMode: "sequential",
|
||||
execute: async (_args, api, callContext) => {
|
||||
api.output("partial");
|
||||
|
|
@ -1218,7 +1218,7 @@ describe("tool rounds", () => {
|
|||
defineTool({
|
||||
name: "noop",
|
||||
description: "Does nothing",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => ({ output: [] }),
|
||||
}),
|
||||
);
|
||||
|
|
@ -1298,7 +1298,7 @@ describe("tool rounds", () => {
|
|||
defineTool({
|
||||
name: "noop",
|
||||
description: "Does nothing",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => ({ output: [] }),
|
||||
}),
|
||||
);
|
||||
|
|
@ -1826,7 +1826,7 @@ describe("tool rounds as the view shows them", () => {
|
|||
defineTool({
|
||||
name: "noop",
|
||||
description: "Does nothing",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => ({ output: [] }),
|
||||
}),
|
||||
);
|
||||
|
|
@ -1866,7 +1866,7 @@ describe("tool rounds as the view shows them", () => {
|
|||
defineTool({
|
||||
name: "delegate",
|
||||
description: "Starts work in a conversation it owns",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api, callContext) => {
|
||||
await api.commit(async (tx) => {
|
||||
const child = await tx.createConversation({ ownership: { kind: "task", taskId: api.taskId } });
|
||||
|
|
@ -1906,7 +1906,7 @@ describe("tool rounds as the view shows them", () => {
|
|||
defineTool({
|
||||
name: "broken",
|
||||
description: "Starts owned work, then returns a result that is not strict JSON",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api, callContext) => {
|
||||
await api.commit((tx) => tx.createTask(Node, { name: "held" }, owned(api.taskId)), callContext);
|
||||
return { output: [], details: { fn: (() => 1) as unknown as JsonValue } };
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// Admission for hosts that must not start runs: `whenIdle: "reject"`, and `submit(tx, draft)` inside the caller's
|
||||
// commit (#11149).
|
||||
|
||||
import { withTelemetry } from "@earendil-works/chord/context";
|
||||
import {
|
||||
type AssistantMessage,
|
||||
|
|
@ -7,7 +8,6 @@ import {
|
|||
fauxAssistantMessage,
|
||||
fauxText,
|
||||
fauxToolCall,
|
||||
Type,
|
||||
} from "@earendil-works/pi-ai";
|
||||
import {
|
||||
type Conversation,
|
||||
|
|
@ -26,6 +26,7 @@ import {
|
|||
type Tx,
|
||||
UserEntry,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { InMemoryTelemetryContext } from "@earendil-works/pi-telemetry";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { type ChatSetup, chatSetup, openChat, unanswered, waitFor } from "./chat-support.ts";
|
||||
|
|
@ -80,7 +81,7 @@ function addHold(setup: ChatSetup) {
|
|||
defineTool({
|
||||
name: "hold",
|
||||
description: "Holds until released",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => {
|
||||
reached.resolve();
|
||||
await release.promise;
|
||||
|
|
@ -180,7 +181,7 @@ describe("whenIdle reject", () => {
|
|||
defineTool({
|
||||
name: "nudge",
|
||||
description: "Steers another conversation",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api, callContext) => {
|
||||
const handle = (await api.conversation(other!.id, callContext))!;
|
||||
const draft = { type: "input", content: "nudge", whenBusy: "steer", whenIdle: "reject" } as const;
|
||||
|
|
@ -525,7 +526,7 @@ describe("submit inside the caller's commit", () => {
|
|||
defineTool({
|
||||
name: "delegate",
|
||||
description: "Delegates to another conversation",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api, callContext) => {
|
||||
toolTask = api.taskId;
|
||||
const handle = (await api.conversation(other!.id, callContext))!;
|
||||
|
|
@ -611,7 +612,7 @@ describe("submit inside the caller's commit", () => {
|
|||
defineTool({
|
||||
name: "hold",
|
||||
description: "Holds until released, also when aborted",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api, callContext) => {
|
||||
handle = (await api.conversation(other!.id, callContext))!;
|
||||
callContext.abortSignal!.addEventListener("abort", () => abortSeen.resolve(), { once: true });
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import type {
|
|||
ToolResultMessage,
|
||||
UserMessage,
|
||||
} from "@earendil-works/pi-ai";
|
||||
import { createModels, Type } from "@earendil-works/pi-ai";
|
||||
import { createModels } from "@earendil-works/pi-ai";
|
||||
import {
|
||||
type AnyTask,
|
||||
createRegistry,
|
||||
|
|
@ -23,10 +23,11 @@ import {
|
|||
section,
|
||||
type ToolRegistration,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { context } from "./session-support.ts";
|
||||
|
||||
export function tool(name: string, description = `${name} tool`): ToolRegistration {
|
||||
return defineTool({ name, description, parameters: Type.Object({}), execute: async () => ({ output: [] }) });
|
||||
return defineTool({ name, description, parameters: obj({}), execute: async () => ({ output: [] }) });
|
||||
}
|
||||
|
||||
/** Open a Harness with a fresh registry holding the named tools. */
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { mkdtemp, rm } from "node:fs/promises";
|
|||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import type { Context, JsonValue } from "@earendil-works/chord";
|
||||
import { createModels, Type } from "@earendil-works/pi-ai";
|
||||
import { createModels } from "@earendil-works/pi-ai";
|
||||
import {
|
||||
createRegistry,
|
||||
defineDoc,
|
||||
|
|
@ -15,6 +15,7 @@ import {
|
|||
type TaskId,
|
||||
type TaskRuntime,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { openNodeSqliteStorage } from "../src/storage/sqlite/node.ts";
|
||||
import { addTask, addTool } from "./harness-support.ts";
|
||||
|
|
@ -482,7 +483,7 @@ describe("blocked tasks", () => {
|
|||
defineTool({
|
||||
name: "unrelated",
|
||||
description: "unrelated",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => ({}),
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { Context } from "@earendil-works/chord";
|
||||
import { withAbortSignal, withCancel } from "@earendil-works/chord/context";
|
||||
import { createModels, Type } from "@earendil-works/pi-ai";
|
||||
import { createModels } from "@earendil-works/pi-ai";
|
||||
import {
|
||||
type CommitPublication,
|
||||
type Conversation,
|
||||
|
|
@ -18,6 +18,7 @@ import {
|
|||
type TaskId,
|
||||
type TaskRuntime,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { InMemoryTelemetryContext } from "@earendil-works/pi-telemetry";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { SessionImpl } from "../src/session/session.ts";
|
||||
|
|
@ -424,7 +425,7 @@ describe("task phases", () => {
|
|||
defineTool({
|
||||
name: "late",
|
||||
description: "late",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => ({}),
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { withTelemetry } from "@earendil-works/chord/context";
|
||||
import { fauxAssistantMessage, fauxToolCall, Type } from "@earendil-works/pi-ai";
|
||||
import { fauxAssistantMessage, fauxToolCall } from "@earendil-works/pi-ai";
|
||||
import { CodemodeSandbox } from "@earendil-works/pi-codemode";
|
||||
import {
|
||||
CompactionTask,
|
||||
|
|
@ -11,6 +11,7 @@ import {
|
|||
type Storage,
|
||||
type TaskId,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj, str } from "@earendil-works/pi-schema";
|
||||
import {
|
||||
InMemoryTelemetryContext,
|
||||
NOOP_TELEMETRY_CONTEXT,
|
||||
|
|
@ -53,7 +54,7 @@ describe("Durable telemetry", () => {
|
|||
defineTool({
|
||||
name: "inner",
|
||||
description: "inner",
|
||||
parameters: Type.Object({ secret: Type.String() }),
|
||||
parameters: obj({ secret: str() }),
|
||||
execute: async () => ({ output: [{ type: "text", text: "private-result" }] }),
|
||||
}),
|
||||
);
|
||||
|
|
@ -62,7 +63,7 @@ describe("Durable telemetry", () => {
|
|||
defineTool({
|
||||
name: "outer",
|
||||
description: "outer",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api, ctx) => {
|
||||
await api.executeTool("inner", { secret: "private-argument" }, ctx);
|
||||
return { output: [{ type: "text", text: "private-output" }] };
|
||||
|
|
@ -347,7 +348,7 @@ describe("Durable telemetry", () => {
|
|||
defineTool({
|
||||
name: "hold",
|
||||
description: "hold",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => {
|
||||
reached.resolve();
|
||||
await gate.promise;
|
||||
|
|
@ -415,7 +416,7 @@ describe("Durable telemetry", () => {
|
|||
defineTool({
|
||||
name: "delegate",
|
||||
description: "delegate",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api, ctx) => {
|
||||
const child = await api.commit(
|
||||
(tx) => tx.createConversation({ ownership: { kind: "task", taskId: api.taskId } }),
|
||||
|
|
@ -952,7 +953,7 @@ describe("Durable telemetry", () => {
|
|||
defineTool({
|
||||
name: "once",
|
||||
description: "once",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => {
|
||||
runs++;
|
||||
return { output: [] };
|
||||
|
|
@ -965,7 +966,7 @@ describe("Durable telemetry", () => {
|
|||
name: "hold",
|
||||
description: "hold",
|
||||
replay: "safe",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, _api, ctx) => {
|
||||
if (!recover) {
|
||||
reached.resolve();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// `afterTool` runs on every result a tool task writes, also those the Harness decides without executing the tool
|
||||
// (#11043).
|
||||
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
|
@ -10,7 +11,6 @@ import {
|
|||
fauxText,
|
||||
fauxToolCall,
|
||||
type ToolResultMessage,
|
||||
Type,
|
||||
} from "@earendil-works/pi-ai";
|
||||
import {
|
||||
defineTool,
|
||||
|
|
@ -29,6 +29,7 @@ import {
|
|||
ToolResultEntry,
|
||||
ToolTask,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { bool, obj, opt, str } from "@earendil-works/pi-schema";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { openNodeSqliteStorage } from "../src/storage/sqlite/node.ts";
|
||||
import { commitWrites } from "../src/testing/commit-writes.ts";
|
||||
|
|
@ -79,7 +80,7 @@ function stored(): Promise<void> {
|
|||
return new Promise((resolve) => setTimeout(resolve, 50));
|
||||
}
|
||||
|
||||
const Parameters = Type.Object({ text: Type.Optional(Type.String()) });
|
||||
const Parameters = obj({ text: opt(str()) });
|
||||
|
||||
function tool(
|
||||
name: string,
|
||||
|
|
@ -318,7 +319,7 @@ describe("afterTool on results the call phase decides", () => {
|
|||
addTool(
|
||||
setup.registry,
|
||||
tool("typed", async () => ({ output: [], structuredOutput: { ok: true } }), {
|
||||
structuredOutputSchema: Type.Object({ ok: Type.Boolean() }),
|
||||
structuredOutputSchema: obj({ ok: bool() }),
|
||||
}),
|
||||
);
|
||||
const nested: NestedToolExecutionResult[] = [];
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {
|
|||
fauxText,
|
||||
fauxToolCall,
|
||||
type ToolResultMessage,
|
||||
Type,
|
||||
} from "@earendil-works/pi-ai";
|
||||
import {
|
||||
defineTool,
|
||||
|
|
@ -21,6 +20,7 @@ import {
|
|||
ToolResultEntry,
|
||||
ToolTask,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { ExecutionEnv } from "../src/env/index.ts";
|
||||
import { NodeExecutionEnv } from "../src/env/node.ts";
|
||||
|
|
@ -55,7 +55,7 @@ function tool(
|
|||
execute: ToolRegistration["execute"],
|
||||
extra: Partial<ToolRegistration> = {},
|
||||
): ToolRegistration {
|
||||
return defineTool({ name, description: name, parameters: Type.Object({}), execute, ...extra });
|
||||
return defineTool({ name, description: name, parameters: obj({}), execute, ...extra });
|
||||
}
|
||||
|
||||
function call(name: string, id = "c1"): AssistantMessage {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import {
|
|||
type Models,
|
||||
type SystemMessage,
|
||||
type ToolResultMessage,
|
||||
Type,
|
||||
} from "@earendil-works/pi-ai";
|
||||
import {
|
||||
AgentDoc,
|
||||
|
|
@ -32,6 +31,7 @@ import {
|
|||
ToolResultEntry,
|
||||
ToolTask,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj, opt, str } from "@earendil-works/pi-schema";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { NodeExecutionEnv } from "../src/env/node.ts";
|
||||
import { createBashTool, createEditTool, createReadTool } from "../src/tools/index.ts";
|
||||
|
|
@ -40,7 +40,7 @@ import { addHooks, addTask, addTool, type Installed } from "./harness-support.ts
|
|||
import { context } from "./session-support.ts";
|
||||
import { aborted, deferred } from "./task-support.ts";
|
||||
|
||||
const EchoParameters = Type.Object({ text: Type.Optional(Type.String()) });
|
||||
const EchoParameters = obj({ text: opt(str()) });
|
||||
type Echo = ToolRegistration<typeof EchoParameters>;
|
||||
type Execute = Echo["execute"];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
// Per-conversation turn limits (`AgentState.maxTurns`) and continuing a run that reached one (#11043).
|
||||
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { type AssistantMessage, fauxAssistantMessage, fauxText, fauxToolCall, Type } from "@earendil-works/pi-ai";
|
||||
import { type AssistantMessage, fauxAssistantMessage, fauxText, fauxToolCall } from "@earendil-works/pi-ai";
|
||||
import {
|
||||
type AfterFailedRun,
|
||||
type Conversation,
|
||||
|
|
@ -16,6 +17,7 @@ import {
|
|||
ToolResultEntry,
|
||||
ToolTask,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { openNodeSqliteStorage } from "../src/storage/sqlite/node.ts";
|
||||
import { allEntries, type ChatSetup, chatSetup, openChat, textOf } from "./chat-support.ts";
|
||||
|
|
@ -33,7 +35,7 @@ afterEach(async () => {
|
|||
directories.clear();
|
||||
});
|
||||
|
||||
const Parameters = Type.Object({});
|
||||
const Parameters = obj({});
|
||||
|
||||
function tool(
|
||||
name: string,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// What a renderer sees of a run through the conversation view: the transcript window and `pi.live`, every change a
|
||||
// commit makes, rebuilt from operations alone. This replaced the event stream (`watchEvents`, #11327).
|
||||
|
||||
import { GCProfiler, getHeapStatistics, setFlagsFromString } from "node:v8";
|
||||
import { runInNewContext } from "node:vm";
|
||||
import type { Draft } from "@earendil-works/chord";
|
||||
|
|
@ -11,7 +12,6 @@ import {
|
|||
fauxText,
|
||||
fauxThinking,
|
||||
fauxToolCall,
|
||||
Type,
|
||||
} from "@earendil-works/pi-ai";
|
||||
import {
|
||||
type Conversation,
|
||||
|
|
@ -22,6 +22,7 @@ import {
|
|||
type LiveState,
|
||||
MemoryStorage,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { num, obj } from "@earendil-works/pi-schema";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { type ChatSetup, chatSetup, openChat, textOf, waitFor } from "./chat-support.ts";
|
||||
import { addTool } from "./harness-support.ts";
|
||||
|
|
@ -126,7 +127,7 @@ describe("a run as the conversation view shows it", () => {
|
|||
defineTool({
|
||||
name: "print",
|
||||
description: "Prints",
|
||||
parameters: Type.Object({ n: Type.Number() }),
|
||||
parameters: obj({ n: num() }),
|
||||
execute: async (_args, api) => {
|
||||
api.output("one\n");
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
|
|
@ -259,7 +260,7 @@ describe("a run as the conversation view shows it", () => {
|
|||
defineTool({
|
||||
name: "tail",
|
||||
description: "Prints lines",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
outputLimits: { maxLines: 3, retain: "tail" },
|
||||
execute: async (_args, api) => {
|
||||
for (let line = 0; line < 6; line++) {
|
||||
|
|
@ -339,7 +340,7 @@ describe("a run as the conversation view shows it", () => {
|
|||
defineTool({
|
||||
name: "wait",
|
||||
description: "Waits until aborted",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: (_args, _api, callContext) => aborted(callContext.abortSignal!),
|
||||
}),
|
||||
);
|
||||
|
|
@ -380,7 +381,7 @@ describe("a run as the conversation view shows it", () => {
|
|||
defineTool({
|
||||
name: "hold",
|
||||
description: "Waits",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => {
|
||||
await gate.promise;
|
||||
return {};
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
*
|
||||
* node --conditions=source --experimental-strip-types --expose-gc test/nested-images-bench.ts [count]
|
||||
*/
|
||||
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { mkdtemp, readdir, rm, stat } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
|
|
@ -18,7 +19,7 @@ import { BACKGROUND_CONTEXT as context } from "@earendil-works/chord/context";
|
|||
import type { ImageContent } from "@earendil-works/pi-ai";
|
||||
import { createModels } from "@earendil-works/pi-ai/models";
|
||||
import { fauxAssistantMessage, fauxProvider, fauxText, fauxToolCall } from "@earendil-works/pi-ai/providers/faux";
|
||||
import { Type } from "typebox";
|
||||
import { arr, lit, num, obj, opt, str } from "@earendil-works/pi-schema";
|
||||
import {
|
||||
createRegistry,
|
||||
defineExtension,
|
||||
|
|
@ -95,11 +96,11 @@ function noWrites(): Written {
|
|||
};
|
||||
}
|
||||
|
||||
const ImageArgument = Type.Object({
|
||||
type: Type.Literal("image"),
|
||||
mimeType: Type.String(),
|
||||
data: Type.Optional(Type.String()),
|
||||
source: Type.Optional(Type.Object({ taskId: Type.Number() })),
|
||||
const ImageArgument = obj({
|
||||
type: lit("image"),
|
||||
mimeType: str(),
|
||||
data: opt(str()),
|
||||
source: opt(obj({ taskId: num() })),
|
||||
});
|
||||
|
||||
function registryFor(scenario: Scenario, probe: Probe) {
|
||||
|
|
@ -107,7 +108,7 @@ function registryFor(scenario: Scenario, probe: Probe) {
|
|||
const readImage = defineTool({
|
||||
name: "read_image",
|
||||
description: "Read an image",
|
||||
parameters: Type.Object({ n: Type.Number() }),
|
||||
parameters: obj({ n: num() }),
|
||||
// One image, so programs get it as an `ImageContent`. Unique per call: base64 of random bytes.
|
||||
execute: async () => ({
|
||||
output: [
|
||||
|
|
@ -118,7 +119,7 @@ function registryFor(scenario: Scenario, probe: Probe) {
|
|||
const classify = defineTool({
|
||||
name: "classify",
|
||||
description: "Classify images",
|
||||
parameters: Type.Object({ images: Type.Array(ImageArgument) }),
|
||||
parameters: obj({ images: arr(ImageArgument) }),
|
||||
execute: async (args, api, callContext) => {
|
||||
for (const image of args.images) {
|
||||
let data = image.data;
|
||||
|
|
@ -141,7 +142,7 @@ function registryFor(scenario: Scenario, probe: Probe) {
|
|||
const script = defineTool({
|
||||
name: "script",
|
||||
description: "Read and classify images",
|
||||
parameters: Type.Object({ count: Type.Number() }),
|
||||
parameters: obj({ count: num() }),
|
||||
callers: ["model"],
|
||||
execute: async (args, api, callContext) => {
|
||||
for (let n = 0; n < args.count; n++) {
|
||||
|
|
|
|||
|
|
@ -9,13 +9,14 @@
|
|||
*
|
||||
* node --conditions=source --experimental-strip-types --expose-gc test/nested-tools-bench.ts [streaming|nesting]
|
||||
*/
|
||||
|
||||
import { mkdtemp, readdir, rm, stat } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { BACKGROUND_CONTEXT as context } from "@earendil-works/chord/context";
|
||||
import { createModels } from "@earendil-works/pi-ai/models";
|
||||
import { fauxAssistantMessage, fauxProvider, fauxText, fauxToolCall } from "@earendil-works/pi-ai/providers/faux";
|
||||
import { Type } from "typebox";
|
||||
import { num, obj } from "@earendil-works/pi-schema";
|
||||
import {
|
||||
type Conversation,
|
||||
createRegistry,
|
||||
|
|
@ -103,7 +104,7 @@ async function runScenario(scenario: Scenario): Promise<{ perRun: Run[]; closedB
|
|||
const blob = defineTool({
|
||||
name: "blob",
|
||||
description: "Stream a blob",
|
||||
parameters: Type.Object({ n: Type.Number() }),
|
||||
parameters: obj({ n: num() }),
|
||||
execute: async (_args, api) => {
|
||||
for (let n = 0; n < CHUNKS; n++) {
|
||||
api.output(chunk);
|
||||
|
|
@ -118,7 +119,7 @@ async function runScenario(scenario: Scenario): Promise<{ perRun: Run[]; closedB
|
|||
defineTool({
|
||||
name,
|
||||
description: `Call ${target} in parallel`,
|
||||
parameters: Type.Object({ count: Type.Number() }),
|
||||
parameters: obj({ count: num() }),
|
||||
execute: async (args, api, callContext) => {
|
||||
const calls = Array.from({ length: args.count }, (_, n) =>
|
||||
api.executeTool(target, { n, count: FAN_OUT }, callContext, options),
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@
|
|||
*
|
||||
* node --conditions=source --experimental-strip-types test/scheduler-fanout-bench.ts [calls...]
|
||||
*/
|
||||
|
||||
import { BACKGROUND_CONTEXT as context } from "@earendil-works/chord/context";
|
||||
import { createModels } from "@earendil-works/pi-ai/models";
|
||||
import { fauxAssistantMessage, fauxProvider, fauxText, fauxToolCall } from "@earendil-works/pi-ai/providers/faux";
|
||||
import { Type } from "typebox";
|
||||
import { num, obj } from "@earendil-works/pi-schema";
|
||||
import { createRegistry, defineExtension, defineTool, Harness, ToolResultEntry } from "../src/index.ts";
|
||||
import { MemoryStorage } from "../src/storage/memory.ts";
|
||||
|
||||
|
|
@ -20,13 +21,13 @@ async function run(calls: number): Promise<{ ms: number; commits: number }> {
|
|||
const leaf = defineTool({
|
||||
name: "leaf",
|
||||
description: "Return at once",
|
||||
parameters: Type.Object({ n: Type.Number() }),
|
||||
parameters: obj({ n: num() }),
|
||||
execute: async () => ({ output: [{ type: "text", text: "ok" }] }),
|
||||
});
|
||||
const fan = defineTool({
|
||||
name: "fan",
|
||||
description: "Call leaf in parallel",
|
||||
parameters: Type.Object({ count: Type.Number() }),
|
||||
parameters: obj({ count: num() }),
|
||||
execute: async (args, api, callContext) => {
|
||||
const results = await Promise.all(
|
||||
Array.from({ length: args.count }, (_, n) =>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// The Session's document cache is bounded by use: unattached documents leave it after the retention period and load
|
||||
// from Storage again; documents with a watch or a document state never leave it.
|
||||
import { fauxAssistantMessage, fauxText, fauxToolCall, Type } from "@earendil-works/pi-ai";
|
||||
|
||||
import { fauxAssistantMessage, fauxText, fauxToolCall } from "@earendil-works/pi-ai";
|
||||
import {
|
||||
type ConversationId,
|
||||
defineDoc,
|
||||
|
|
@ -11,6 +12,7 @@ import {
|
|||
MemoryStorage,
|
||||
type StorageWrite,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { describe, expect, it, onTestFinished } from "vitest";
|
||||
import { SessionImpl, setDefaultDocumentRetention } from "../src/session/session.ts";
|
||||
import { chatSetup, openChat } from "./chat-support.ts";
|
||||
|
|
@ -519,7 +521,7 @@ describe("document cache retention", () => {
|
|||
defineTool({
|
||||
name: "steps",
|
||||
description: "Streams three steps",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async (_args, api) => {
|
||||
for (const step of ["a\n", "b\n", "c\n"]) {
|
||||
api.output(step);
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
* the names the spec leaves to the application declared below. Keep the two in sync; `test/examples/` runs the same
|
||||
* patterns end to end.
|
||||
*/
|
||||
|
||||
import type { Context, Draft } from "@earendil-works/chord";
|
||||
import { type AssistantMessage, type Models, type ToolCall, Type } from "@earendil-works/pi-ai";
|
||||
import type { AssistantMessage, Models, ToolCall } from "@earendil-works/pi-ai";
|
||||
import {
|
||||
type Conversation,
|
||||
type ConversationId,
|
||||
|
|
@ -34,6 +35,7 @@ import {
|
|||
type UserInput,
|
||||
wrapTool,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj, str } from "@earendil-works/pi-schema";
|
||||
import { expect, expectTypeOf, it } from "vitest";
|
||||
import type { ExecutionEnv } from "../src/env/index.ts";
|
||||
import { CodingTools, createBashTool, createEditTool, createReadTool } from "../src/tools/index.ts";
|
||||
|
|
@ -169,7 +171,7 @@ function examples() {
|
|||
defineTool({
|
||||
name: "subagent",
|
||||
description: "Delegate a self-contained task to a subagent and get its answer back.",
|
||||
parameters: Type.Object({ task: Type.String() }),
|
||||
parameters: obj({ task: str() }),
|
||||
replay: "safe",
|
||||
execute: async (args, api, context) => {
|
||||
const { task } = args;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import {
|
|||
type Model,
|
||||
type Models,
|
||||
normalizeContext,
|
||||
Type,
|
||||
uuidv7,
|
||||
} from "@earendil-works/pi-ai";
|
||||
import { builtinModels } from "@earendil-works/pi-ai/providers/all";
|
||||
|
|
@ -18,6 +17,7 @@ import {
|
|||
hook,
|
||||
MemoryStorage,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { obj, str } from "@earendil-works/pi-schema";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { streamSimple as streamSimpleAnthropic } from "../../ai/src/api/anthropic-messages.ts";
|
||||
import { resolveApiKey } from "../../ai/test/oauth.ts";
|
||||
|
|
@ -41,7 +41,7 @@ function instructions(nonce: string): string {
|
|||
return lines.join("\n");
|
||||
}
|
||||
|
||||
const Parameters = Type.Object({ text: Type.String() });
|
||||
const Parameters = obj({ text: str() });
|
||||
const first = defineTool({
|
||||
name: "probe_first",
|
||||
description: "Unused probe tool.",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
* then close the Harness mid-round (persistent backends) to measure replay of the round's delta chain on reopen, and
|
||||
* finish the run to measure the stored size after the round's base and reclamation. Throughput scenarios push 1 GiB.
|
||||
*/
|
||||
|
||||
import { execFile } from "node:child_process";
|
||||
import { mkdtemp, open, readdir, rm, stat } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
|
|
@ -16,7 +17,7 @@ import { promisify } from "node:util";
|
|||
import { BACKGROUND_CONTEXT as context } from "@earendil-works/chord/context";
|
||||
import { createModels } from "@earendil-works/pi-ai/models";
|
||||
import { fauxAssistantMessage, fauxProvider, fauxText, fauxToolCall } from "@earendil-works/pi-ai/providers/faux";
|
||||
import { Type } from "typebox";
|
||||
import { obj } from "@earendil-works/pi-schema";
|
||||
import { NodeExecutionEnv } from "../src/env/node.ts";
|
||||
import { createRegistry, defineTool, Harness, LiveDoc, MemoryStorage, type Storage } from "../src/index.ts";
|
||||
import { openNodeJsonlStorage } from "../src/storage/jsonl/node.ts";
|
||||
|
|
@ -145,7 +146,7 @@ async function runScenario(scenario: Scenario): Promise<Metrics> {
|
|||
defineTool({
|
||||
name: "emit",
|
||||
description: "",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
outputLimits: { retain: scenario.retain },
|
||||
execute: async (_args, api, callContext) => {
|
||||
let counter = 0;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { type AssistantMessage, fauxAssistantMessage, fauxText, fauxToolCall, Type } from "@earendil-works/pi-ai";
|
||||
import { type AssistantMessage, fauxAssistantMessage, fauxText, fauxToolCall } from "@earendil-works/pi-ai";
|
||||
import {
|
||||
AgentDoc,
|
||||
type Conversation,
|
||||
|
|
@ -9,6 +9,7 @@ import {
|
|||
MemoryStorage,
|
||||
type ToolRegistration,
|
||||
} from "@earendil-works/pi-durable";
|
||||
import { num, obj, opt } from "@earendil-works/pi-schema";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createToolSearchExtension } from "../src/tool-search/index.ts";
|
||||
import { allEntries, type ChatSetup, chatSetup, openChat } from "./chat-support.ts";
|
||||
|
|
@ -19,7 +20,7 @@ function tool(name: string, description: string, extra: Partial<ToolRegistration
|
|||
return defineTool({
|
||||
name,
|
||||
description,
|
||||
parameters: Type.Object({ number: Type.Optional(Type.Number({ description: "Issue number" })) }),
|
||||
parameters: obj({ number: opt(num({ description: "Issue number" })) }),
|
||||
execute: async () => ({ output: [{ type: "text", text: `${name} ran` }] }),
|
||||
...extra,
|
||||
});
|
||||
|
|
@ -120,7 +121,7 @@ describe("deferred tools and tool_search", () => {
|
|||
const grow = defineTool({
|
||||
name: "grow",
|
||||
description: "grow",
|
||||
parameters: Type.Object({}),
|
||||
parameters: obj({}),
|
||||
execute: async () => ({ output: [], control: { addTools: ["github_get_issue", "alpha", "ghost"] } }),
|
||||
});
|
||||
addTool(chat.registry, grow);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue