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
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
### Changed
|
||||
|
||||
- Tool argument validation runs on `@earendil-works/pi-schema`. TypeBox features outside JSON Schema (`Type.Refine()`, `Type.BigInt()`, `Type.Undefined()`, codecs, `Format.Set()` formats) no longer apply to tool arguments; see [Tool parameter schemas and TypeBox](docs/extensions.md#tool-parameter-schemas-and-typebox). Validation failures the model sees list pi-schema's messages (`- path: missing`, `- limit: expected number, got string`) instead of TypeBox's (`must have required properties path`, `must be number`); which arguments are accepted and how they are coerced is unchanged for extension and built-in tools.
|
||||
- Extensions keep importing `Type`, `Static` and `TSchema` from `@earendil-works/pi-ai`: Pi maps that import to pi-ai's compat entry plus the TypeBox it bundles, now that pi-ai itself no longer re-exports TypeBox, and its `StringEnum` there stays a TypeBox `TUnsafe<T>`. The built-in tool factories (`createReadTool()` and the others) return `TypeBoxAgentTool<typeof schema>`, which keeps `params` typed for TypeBox.
|
||||
- Tightened system prompt
|
||||
- `pi update` for installs from the pi.dev installer no longer passes `--min-release-age=0` to `npm ci`. The release lockfile pins every package, so the flag had no effect, and security scanners that block it stopped the update ([#11262](https://github.com/earendil-works/pi/issues/11262))
|
||||
- `/model` and `/scoped-models` now show a model's name next to its ID when the name adds words the ID lacks, such as a renamed `models.json` override ([#5916](https://github.com/earendil-works/pi/issues/5916), [#11347](https://github.com/earendil-works/pi/pull/11347))
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ Settings also accept built-in extensions as `builtin:<name>` entries; see [Setti
|
|||
| `@earendil-works/pi-coding-agent` | Extension types (`ExtensionAPI`, `ExtensionContext`, events) and helpers |
|
||||
| `@earendil-works/pi-agent-core` | Agent message and tool types |
|
||||
| `typebox`, `typebox/compile`, `typebox/value` | Schema definitions for tool parameters |
|
||||
| `@earendil-works/pi-ai` | AI utilities (`StringEnum` for string enums, transcript helpers, model types) |
|
||||
| `@earendil-works/pi-ai` | AI utilities (`StringEnum` for string enums, transcript helpers, model types). Inside Pi it also exports TypeBox's `Type`, `Static` and `TSchema`, which the published package no longer re-exports; import them from `typebox` in new code |
|
||||
| `@earendil-works/pi-ai/compat`, `/models`, `/oauth`, `/providers/all` | Provider construction, built-in APIs, and OAuth helpers |
|
||||
| `@earendil-works/pi-tui` | TUI components for custom rendering |
|
||||
|
||||
|
|
@ -169,6 +169,18 @@ npm dependencies work too. Add a `package.json` next to your extension (or in a
|
|||
|
||||
For distributed Pi packages installed with `pi install` (npm or git), runtime deps must be in `dependencies`; `devDependencies` are not installed. Do not list the host-provided packages above in `dependencies`. See [Declare dependencies](packages.md#declare-dependencies).
|
||||
|
||||
### Tool parameter schemas and TypeBox
|
||||
|
||||
Pi validates tool arguments as JSON Schema with `@earendil-works/pi-schema`, whatever built the schema; TypeBox schemas are JSON Schema and work unchanged. Before validation, loosely typed arguments are coerced: numeric strings to numbers, `"true"` to `true`, a single value to a one-item array, `null` in an optional property dropped. TypeBox features outside JSON Schema do not apply to tool arguments:
|
||||
|
||||
- `Type.Refine()` refinements are not run. Check such constraints in `execute()`.
|
||||
- `Type.BigInt()`, `Type.Undefined()` and `Type.Void()` reject every argument, since JSON has no such values. Codecs (`Type.Codec()`) do not decode: `execute()` receives the JSON value.
|
||||
- Formats registered with TypeBox's `Format.Set()` are not checked. The standard formats are, plus `url` and `json-pointer-uri-fragment`.
|
||||
- Property names are matched as names, not as regular expressions, and `additionalProperties` applies only to properties the object does not declare.
|
||||
- A `$ref` that names nothing rejects only arguments at its place.
|
||||
|
||||
Validation errors the model sees use pi-schema's messages, such as `- path: missing` or `- limit: expected number, got string`.
|
||||
|
||||
Node.js built-ins (`node:fs`, `node:path`, etc.) are also available.
|
||||
|
||||
## Writing an Extension
|
||||
|
|
|
|||
180
packages/coding-agent/examples/tsconfig.json
Normal file
180
packages/coding-agent/examples/tsconfig.json
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
// Type-checks the example extensions against the modules extensions see at run time (core/extensions/virtual-modules.ts):
|
||||
// "@earendil-works/pi-ai" is the extension view, which adds TypeBox's Type, Static and TSchema to the compat entry (the
|
||||
// view imports "@earendil-works/pi-ai/compat", so that path stays the package's own).
|
||||
// The other paths repeat the root tsconfig.json; keep them in sync.
|
||||
// Also the test that imports the subagent example: it type-checks only against the extension view.
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@earendil-works/chord": [
|
||||
"../../../packages/chord/src/index.ts"
|
||||
],
|
||||
"@earendil-works/chord/context": [
|
||||
"../../../packages/chord/src/context/index.ts"
|
||||
],
|
||||
"@earendil-works/chord/delta": [
|
||||
"../../../packages/chord/src/delta/index.ts"
|
||||
],
|
||||
"@earendil-works/chord/next": [
|
||||
"../../../packages/chord/src/next/index.ts"
|
||||
],
|
||||
"@earendil-works/chord/next/plugins": [
|
||||
"../../../packages/chord/src/next-node/plugins/index.ts"
|
||||
],
|
||||
"@earendil-works/chord/next/plugins/runtime": [
|
||||
"../../../packages/chord/src/next-node/plugins/runtime.ts"
|
||||
],
|
||||
"@test/contracts/*": [
|
||||
"../../../packages/chord/test/next-node/fixtures/plugins/contracts/*.ts"
|
||||
],
|
||||
"@example/contracts/*": [
|
||||
"../../../packages/chord/test/next/examples/35-plugins/plugins/contracts/*.ts"
|
||||
],
|
||||
"@example/weather/*": [
|
||||
"../../../packages/chord/test/next/examples/36-plugin-failures/plugins/weather/*.ts"
|
||||
],
|
||||
"@example/prices/*": [
|
||||
"../../../packages/chord/test/next/examples/36-plugin-failures/plugins/prices/*.ts"
|
||||
],
|
||||
"@example/shapes/*": [
|
||||
"../../../packages/chord/test/next/examples/37-plugin-imports/plugins/shapes/*.ts"
|
||||
],
|
||||
"@example/shared/*": [
|
||||
"../../../packages/chord/test/next/examples/38-watch-and-rollout/plugins/contracts/*.ts"
|
||||
],
|
||||
"@earendil-works/chord/next/http": [
|
||||
"../../../packages/chord/src/next-node/http/index.ts"
|
||||
],
|
||||
"@earendil-works/chord/next/workers": [
|
||||
"../../../packages/chord/src/next-node/workers/index.ts"
|
||||
],
|
||||
"@earendil-works/chord/node": [
|
||||
"../../../packages/chord/src/node.ts"
|
||||
],
|
||||
"@earendil-works/chord/output": [
|
||||
"../../../packages/chord/src/output.ts"
|
||||
],
|
||||
"@earendil-works/chord/telemetry-sqlite": [
|
||||
"../../../packages/chord/src/telemetry-sqlite/index.ts"
|
||||
],
|
||||
"@earendil-works/chord/sqlite": [
|
||||
"../../../packages/chord/src/sqlite/index.ts"
|
||||
],
|
||||
"@earendil-works/chord/sqlite/node": [
|
||||
"../../../packages/chord/src/sqlite/node.ts"
|
||||
],
|
||||
"@earendil-works/chord/sqlite/cloudflare": [
|
||||
"../../../packages/chord/src/sqlite/cloudflare.ts"
|
||||
],
|
||||
"@earendil-works/pi-telemetry": [
|
||||
"../../../packages/telemetry/src/index.ts"
|
||||
],
|
||||
"@earendil-works/pi-telemetry/testing": [
|
||||
"../../../packages/telemetry/src/testing/index.ts"
|
||||
],
|
||||
"@earendil-works/pi-telemetry/render": [
|
||||
"../../../packages/telemetry/src/render.ts"
|
||||
],
|
||||
"@earendil-works/pi-codemode": [
|
||||
"../../../packages/codemode/src/index.ts"
|
||||
],
|
||||
"@earendil-works/pi-codemode/cloudflare": [
|
||||
"../../../packages/codemode/src/cloudflare.ts"
|
||||
],
|
||||
"@earendil-works/pi-codemode/declarations": [
|
||||
"../../../packages/codemode/src/declarations.ts"
|
||||
],
|
||||
"@earendil-works/pi-codemode/portable": [
|
||||
"../../../packages/codemode/src/portable.ts"
|
||||
],
|
||||
"@earendil-works/pi-codemode/source": [
|
||||
"../../../packages/codemode/src/source.ts"
|
||||
],
|
||||
"@earendil-works/pi-codemode/worker": [
|
||||
"../../../packages/codemode/src/runtime/worker.ts"
|
||||
],
|
||||
"@earendil-works/pi-schema": [
|
||||
"../../../packages/schema/src/index.ts"
|
||||
],
|
||||
"@earendil-works/pi-schema/formats": [
|
||||
"../../../packages/schema/src/formats/index.ts"
|
||||
],
|
||||
"@earendil-works/pi-mcp": [
|
||||
"../../../packages/mcp/src/index.ts"
|
||||
],
|
||||
"@earendil-works/pi-mcp/oauth": [
|
||||
"../../../packages/mcp/src/oauth/index.ts"
|
||||
],
|
||||
"@earendil-works/pi-mcp/testing": [
|
||||
"../../../packages/mcp/src/testing/index.ts"
|
||||
],
|
||||
"@earendil-works/pi-ai": [
|
||||
"../../../packages/coding-agent/src/core/extensions/pi-ai-extension-view.ts"
|
||||
],
|
||||
"@earendil-works/pi-ai/oauth": [
|
||||
"../../../packages/ai/src/oauth.ts"
|
||||
],
|
||||
"@earendil-works/pi-ai/*": [
|
||||
"../../../packages/ai/src/*.ts",
|
||||
"../../../packages/ai/src/providers/*.ts"
|
||||
],
|
||||
"@earendil-works/pi-ai/dist/*": [
|
||||
"../../../packages/ai/src/*"
|
||||
],
|
||||
"@earendil-works/pi-durable": [
|
||||
"../../../packages/durable/src/index.ts"
|
||||
],
|
||||
"@earendil-works/pi-durable/testing": [
|
||||
"../../../packages/durable/src/testing/index.ts"
|
||||
],
|
||||
"@earendil-works/pi-durable/*": [
|
||||
"../../../packages/durable/src/*.ts",
|
||||
"../../../packages/durable/src/*/index.ts",
|
||||
"../../../packages/durable/src/*"
|
||||
],
|
||||
"@earendil-works/pi-env": [
|
||||
"../../../packages/env/src/index.ts"
|
||||
],
|
||||
"@earendil-works/pi-agent-core": [
|
||||
"../../../packages/agent/src/index.ts"
|
||||
],
|
||||
"@earendil-works/pi-coding-agent": [
|
||||
"../../../packages/coding-agent/src/index.ts"
|
||||
],
|
||||
"@earendil-works/pi-coding-agent/hooks": [
|
||||
"../../../packages/coding-agent/src/core/hooks/index.ts"
|
||||
],
|
||||
"@earendil-works/pi-coding-agent/*": [
|
||||
"../../../packages/coding-agent/src/*"
|
||||
],
|
||||
"typebox": [
|
||||
"../../../node_modules/typebox"
|
||||
],
|
||||
"@earendil-works/pi-tui": [
|
||||
"../../../packages/tui/src/index.ts"
|
||||
],
|
||||
"@earendil-works/pi-tui/*": [
|
||||
"../../../packages/tui/src/*"
|
||||
],
|
||||
"@earendil-works/pi-agent-old": [
|
||||
"../../../packages/agent-old/src/index.ts"
|
||||
],
|
||||
"@earendil-works/pi-agent-old/*": [
|
||||
"../../../packages/agent-old/src/*"
|
||||
],
|
||||
"@mariozechner/pi-ai": [
|
||||
"../../../packages/coding-agent/src/core/extensions/pi-ai-extension-view.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"./extensions/**/*",
|
||||
"../../*/src/**/*.d.ts",
|
||||
"../test/suite/regressions/8261-subagent-project-trust.test.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**",
|
||||
"./extensions/gondolin/**"
|
||||
]
|
||||
}
|
||||
14
packages/coding-agent/install-lock/package-lock.json
generated
14
packages/coding-agent/install-lock/package-lock.json
generated
|
|
@ -391,7 +391,7 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@earendil-works/pi-ai": "1.4.0",
|
||||
"typebox": "1.3.27"
|
||||
"@earendil-works/pi-schema": "1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.19.0"
|
||||
|
|
@ -404,14 +404,14 @@
|
|||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "0.129.0",
|
||||
"@aws-sdk/client-bedrock-runtime": "3.1127.0",
|
||||
"@earendil-works/pi-schema": "1.4.0",
|
||||
"@earendil-works/pi-telemetry": "1.4.0",
|
||||
"@google/genai": "2.21.0",
|
||||
"@smithy/node-http-handler": "4.12.1",
|
||||
"http-proxy-agent": "9.1.0",
|
||||
"https-proxy-agent": "9.1.0",
|
||||
"openai": "7.19.0",
|
||||
"partial-json": "0.1.7",
|
||||
"typebox": "1.3.27"
|
||||
"partial-json": "0.1.7"
|
||||
},
|
||||
"bin": {
|
||||
"pi-ai": "dist/cli.js"
|
||||
|
|
@ -537,6 +537,14 @@
|
|||
"node": ">=22.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@earendil-works/pi-schema": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@earendil-works/pi-schema/-/pi-schema-1.4.0.tgz",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=22.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@earendil-works/pi-telemetry": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@earendil-works/pi-telemetry/-/pi-telemetry-1.4.0.tgz",
|
||||
|
|
|
|||
|
|
@ -255,13 +255,6 @@
|
|||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"inputTokensAbove",
|
||||
"input",
|
||||
"output",
|
||||
"cacheRead",
|
||||
"cacheWrite"
|
||||
],
|
||||
"properties": {
|
||||
"inputTokensAbove": {
|
||||
"type": "number",
|
||||
|
|
@ -283,7 +276,14 @@
|
|||
"type": "number",
|
||||
"description": "Cache-write cost in USD per million tokens."
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"inputTokensAbove",
|
||||
"input",
|
||||
"output",
|
||||
"cacheRead",
|
||||
"cacheWrite"
|
||||
]
|
||||
},
|
||||
"description": "Request-wide pricing tiers. The highest matching input threshold applies to the full request."
|
||||
}
|
||||
|
|
@ -383,12 +383,6 @@
|
|||
"$defs": {
|
||||
"ModelCost": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"input",
|
||||
"output",
|
||||
"cacheRead",
|
||||
"cacheWrite"
|
||||
],
|
||||
"properties": {
|
||||
"input": {
|
||||
"type": "number",
|
||||
|
|
@ -410,13 +404,6 @@
|
|||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"inputTokensAbove",
|
||||
"input",
|
||||
"output",
|
||||
"cacheRead",
|
||||
"cacheWrite"
|
||||
],
|
||||
"properties": {
|
||||
"inputTokensAbove": {
|
||||
"type": "number",
|
||||
|
|
@ -438,11 +425,24 @@
|
|||
"type": "number",
|
||||
"description": "Cache-write cost in USD per million tokens."
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"inputTokensAbove",
|
||||
"input",
|
||||
"output",
|
||||
"cacheRead",
|
||||
"cacheWrite"
|
||||
]
|
||||
},
|
||||
"description": "Request-wide pricing tiers. The highest matching input threshold applies to the full request."
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"input",
|
||||
"output",
|
||||
"cacheRead",
|
||||
"cacheWrite"
|
||||
]
|
||||
},
|
||||
"ModelInputLimits": {
|
||||
"type": "object",
|
||||
|
|
@ -628,9 +628,6 @@
|
|||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"$var"
|
||||
],
|
||||
"properties": {
|
||||
"$var": {
|
||||
"anyOf": [
|
||||
|
|
@ -651,7 +648,10 @@
|
|||
"omitWhenOff": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"$var"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -677,9 +677,6 @@
|
|||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"$var"
|
||||
],
|
||||
"properties": {
|
||||
"$var": {
|
||||
"anyOf": [
|
||||
|
|
@ -700,7 +697,10 @@
|
|||
"omitWhenOff": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"$var"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1085,11 +1085,6 @@
|
|||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"provider",
|
||||
"model",
|
||||
"cost"
|
||||
],
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
|
|
@ -1103,6 +1098,11 @@
|
|||
"$ref": "#/$defs/ModelCost"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"provider",
|
||||
"model",
|
||||
"cost"
|
||||
],
|
||||
"description": "An Anthropic server-side refusal fallback model with local pricing metadata."
|
||||
},
|
||||
"maxItems": 3,
|
||||
|
|
|
|||
|
|
@ -88,10 +88,11 @@ function getAliases(): Record<string, string> {
|
|||
const piCodingAgentEntry = packageIndex;
|
||||
const piAgentCoreEntry = resolveWorkspaceOrImport("agent/dist/index.js", "@earendil-works/pi-agent-core");
|
||||
const piTuiEntry = resolveWorkspaceOrImport("tui/dist/index.js", "@earendil-works/pi-tui");
|
||||
// Extensions resolve the pi-ai root to the compat entrypoint (a strict
|
||||
// superset of the core entrypoint): existing extensions using the old
|
||||
// global API keep working at runtime until compat is removed.
|
||||
const piAiCompatEntry = resolveWorkspaceOrImport("ai/dist/compat.js", "@earendil-works/pi-ai/compat");
|
||||
// Extensions resolve the pi-ai root to the extension view: the compat
|
||||
// entrypoint (a strict superset of the core entrypoint) plus TypeBox's Type,
|
||||
// Static and TSchema, so extensions using the old global API and pi-ai's
|
||||
// former TypeBox re-exports keep working.
|
||||
const piAiCompatEntry = path.resolve(__dirname, "pi-ai-extension-view.js");
|
||||
const piAiModelsEntry = resolveWorkspaceOrImport("ai/dist/models.js", "@earendil-works/pi-ai/models");
|
||||
const piAiOauthEntry = resolveWorkspaceOrImport("ai/dist/oauth.js", "@earendil-works/pi-ai/oauth");
|
||||
const piAiProvidersEntry = resolveWorkspaceOrImport(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* The `@earendil-works/pi-ai` module extensions import: pi-ai's compat entry point plus TypeBox's `Type`, `Static` and
|
||||
* `TSchema`, which pi-ai's main entry no longer re-exports, and a `StringEnum` built with TypeBox (`TUnsafe<T>`), so
|
||||
* extensions that use it inside `Type.Object` type-check and infer as before. The TypeBox is the coding agent's own,
|
||||
* the same one extensions get for `typebox`.
|
||||
*/
|
||||
|
||||
import { type TUnsafe, Type } from "typebox";
|
||||
|
||||
export * from "@earendil-works/pi-ai/compat";
|
||||
export type { Static, TSchema } from "typebox";
|
||||
export { Type };
|
||||
|
||||
/**
|
||||
* Creates a string enum schema compatible with Google's API and other providers that don't support anyOf/const
|
||||
* patterns: `{ type: "string", enum: [...] }`, typed for TypeBox's `Static`.
|
||||
*/
|
||||
export function StringEnum<T extends readonly string[]>(
|
||||
values: T,
|
||||
options?: { description?: string; default?: T[number] },
|
||||
): TUnsafe<T[number]> {
|
||||
return Type.Unsafe<T[number]>({
|
||||
type: "string",
|
||||
enum: values as unknown as T[number][],
|
||||
...(options?.description && { description: options.description }),
|
||||
...(options?.default && { default: options.default }),
|
||||
});
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import * as bundledPiAgentCore from "@earendil-works/pi-agent-core";
|
||||
import * as bundledPiAiCompat from "@earendil-works/pi-ai/compat";
|
||||
import * as bundledPiAiModels from "@earendil-works/pi-ai/models";
|
||||
import * as bundledPiAiOauth from "@earendil-works/pi-ai/oauth";
|
||||
import * as bundledPiAiProviders from "@earendil-works/pi-ai/providers/all";
|
||||
|
|
@ -10,6 +9,7 @@ import * as bundledTypeboxValue from "typebox/value";
|
|||
// This import is safe because loader.ts exports are not re-exported from index.ts.
|
||||
// Extensions can therefore import from @earendil-works/pi-coding-agent.
|
||||
import * as bundledPiCodingAgent from "../../index.ts";
|
||||
import * as bundledPiAiExtensionView from "./pi-ai-extension-view.ts";
|
||||
|
||||
/** Modules available to extensions in source and compiled binary runtimes. */
|
||||
export const VIRTUAL_MODULES: Record<string, unknown> = {
|
||||
|
|
@ -22,18 +22,19 @@ export const VIRTUAL_MODULES: Record<string, unknown> = {
|
|||
"@earendil-works/pi-agent-core": bundledPiAgentCore,
|
||||
"@earendil-works/pi-tui": bundledPiTui,
|
||||
// Extensions resolve the pi-ai root to the compat entrypoint (a strict
|
||||
// superset of the core entrypoint): existing extensions using the old
|
||||
// global API keep working at runtime until compat is removed.
|
||||
"@earendil-works/pi-ai": bundledPiAiCompat,
|
||||
"@earendil-works/pi-ai/compat": bundledPiAiCompat,
|
||||
// superset of the core entrypoint) plus TypeBox's Type, Static and TSchema:
|
||||
// existing extensions using the old global API and pi-ai's former TypeBox
|
||||
// re-exports keep working.
|
||||
"@earendil-works/pi-ai": bundledPiAiExtensionView,
|
||||
"@earendil-works/pi-ai/compat": bundledPiAiExtensionView,
|
||||
"@earendil-works/pi-ai/models": bundledPiAiModels,
|
||||
"@earendil-works/pi-ai/oauth": bundledPiAiOauth,
|
||||
"@earendil-works/pi-ai/providers/all": bundledPiAiProviders,
|
||||
"@earendil-works/pi-coding-agent": bundledPiCodingAgent,
|
||||
"@mariozechner/pi-agent-core": bundledPiAgentCore,
|
||||
"@mariozechner/pi-tui": bundledPiTui,
|
||||
"@mariozechner/pi-ai": bundledPiAiCompat,
|
||||
"@mariozechner/pi-ai/compat": bundledPiAiCompat,
|
||||
"@mariozechner/pi-ai": bundledPiAiExtensionView,
|
||||
"@mariozechner/pi-ai/compat": bundledPiAiExtensionView,
|
||||
"@mariozechner/pi-ai/models": bundledPiAiModels,
|
||||
"@mariozechner/pi-ai/oauth": bundledPiAiOauth,
|
||||
"@mariozechner/pi-ai/providers/all": bundledPiAiProviders,
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
/** Immutable, credential-blind models.json snapshot. */
|
||||
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { ProviderCompatSchema } from "@earendil-works/pi-ai/providers/compat-schema";
|
||||
import {
|
||||
ModelCostSchema,
|
||||
ModelInputLimitsSchema,
|
||||
ModelInputModalitySchema,
|
||||
ModelPromptCacheSchema,
|
||||
ThinkingLevelMapSchema,
|
||||
} from "@earendil-works/pi-ai/providers/model-schema";
|
||||
import { type Static, Type } from "typebox";
|
||||
import { Compile } from "typebox/compile";
|
||||
import type { TLocalizedValidationError } from "typebox/error";
|
||||
import { stripJsonComments } from "../utils/json.ts";
|
||||
import { normalizePath } from "../utils/paths.ts";
|
||||
import { stripBom } from "../utils/text.ts";
|
||||
import {
|
||||
ModelCostSchema,
|
||||
ModelInputLimitsSchema,
|
||||
ModelInputModalitySchema,
|
||||
ModelPromptCacheSchema,
|
||||
PartialModelCostSchema,
|
||||
ProviderCompatSchema,
|
||||
ThinkingLevelMapSchema,
|
||||
} from "./pi-ai-schemas.ts";
|
||||
|
||||
const SamplingParamsSchema = Type.Record(Type.String(), Type.Unknown());
|
||||
const SamplingParamsByThinkingLevelSchema = Type.Object(
|
||||
|
|
@ -74,7 +75,7 @@ const ModelOverrideSchema = Type.Object({
|
|||
thinkingLevelMap: Type.Optional(ThinkingLevelMapSchema),
|
||||
input: Type.Optional(Type.Array(ModelInputModalitySchema)),
|
||||
inputLimits: Type.Optional(ModelInputLimitsSchema),
|
||||
cost: Type.Optional(Type.Partial(ModelCostSchema)),
|
||||
cost: Type.Optional(PartialModelCostSchema),
|
||||
promptCache: Type.Optional(ModelPromptCacheSchema),
|
||||
contextWindow: Type.Optional(
|
||||
Type.Number({ exclusiveMinimum: 0, description: ModelFieldDescriptions.contextWindow }),
|
||||
|
|
|
|||
40
packages/coding-agent/src/core/pi-ai-schemas.ts
Normal file
40
packages/coding-agent/src/core/pi-ai-schemas.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* pi-ai's configuration schemas (built with pi-schema) as TypeBox schemas, for the coding agent's own TypeBox-built
|
||||
* configuration schemas (models.json, settings.json): the same JSON, typed for TypeBox's `Static`.
|
||||
*/
|
||||
|
||||
import {
|
||||
type ProviderCompat,
|
||||
ProviderCompatSchema as ProviderCompatJsonSchema,
|
||||
} from "@earendil-works/pi-ai/providers/compat-schema";
|
||||
import {
|
||||
type ModelCost,
|
||||
ModelCostSchema as ModelCostJsonSchema,
|
||||
type ModelInputLimits,
|
||||
ModelInputLimitsSchema as ModelInputLimitsJsonSchema,
|
||||
type ModelInputModality,
|
||||
ModelInputModalitySchema as ModelInputModalityJsonSchema,
|
||||
type ModelPromptCache,
|
||||
ModelPromptCacheSchema as ModelPromptCacheJsonSchema,
|
||||
type ModelThinkingLevel,
|
||||
ModelThinkingLevelSchema as ModelThinkingLevelJsonSchema,
|
||||
type ThinkingLevelMap,
|
||||
ThinkingLevelMapSchema as ThinkingLevelMapJsonSchema,
|
||||
} from "@earendil-works/pi-ai/providers/model-schema";
|
||||
import { Type } from "typebox";
|
||||
|
||||
export const ProviderCompatSchema = Type.Unsafe<ProviderCompat>(ProviderCompatJsonSchema);
|
||||
export const ModelCostSchema = Type.Unsafe<ModelCost>(ModelCostJsonSchema);
|
||||
export const ModelInputLimitsSchema = Type.Unsafe<ModelInputLimits>(ModelInputLimitsJsonSchema);
|
||||
export const ModelInputModalitySchema = Type.Unsafe<ModelInputModality>(ModelInputModalityJsonSchema);
|
||||
export const ModelPromptCacheSchema = Type.Unsafe<ModelPromptCache>(ModelPromptCacheJsonSchema);
|
||||
export const ModelThinkingLevelSchema = Type.Unsafe<ModelThinkingLevel>(ModelThinkingLevelJsonSchema);
|
||||
export const ThinkingLevelMapSchema = Type.Unsafe<ThinkingLevelMap>(ThinkingLevelMapJsonSchema);
|
||||
|
||||
/** `ModelCostSchema` with every field optional, for model overrides that change some of the rates. */
|
||||
export const PartialModelCostSchema = Type.Unsafe<Partial<ModelCost>>(withoutRequired(ModelCostJsonSchema));
|
||||
|
||||
function withoutRequired(schema: object): object {
|
||||
const { required: _required, ...rest } = schema as { required?: unknown };
|
||||
return rest;
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { ModelThinkingLevelSchema } from "@earendil-works/pi-ai/providers/model-schema";
|
||||
import { type Static, type TSchemaOptions, Type } from "typebox";
|
||||
import { ModelThinkingLevelSchema } from "./pi-ai-schemas.ts";
|
||||
import { SETTINGS_DEFAULTS } from "./settings-defaults.ts";
|
||||
|
||||
function nonNegativeSafeInteger(options: { default?: number } = {}) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { constants } from "node:fs";
|
||||
import { access as fsAccess } from "node:fs/promises";
|
||||
import { constants as osConstants } from "node:os";
|
||||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import { spawn } from "child_process";
|
||||
import { type Static, Type } from "typebox";
|
||||
import { waitForChildProcess } from "../../utils/child-process.ts";
|
||||
|
|
@ -16,7 +15,7 @@ import {
|
|||
import type { ExtensionContext, ToolDefinition } from "../extensions/types.ts";
|
||||
import { OutputAccumulator } from "./output-accumulator.ts";
|
||||
import { BASH_UPDATE_THROTTLE_MS, createShellRenderers } from "./renderers/bash.ts";
|
||||
import { wrapToolDefinition } from "./tool-definition-wrapper.ts";
|
||||
import { type TypeBoxAgentTool, wrapToolDefinition } from "./tool-definition-wrapper.ts";
|
||||
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult } from "./truncate.ts";
|
||||
|
||||
const MAX_TIMEOUT_MS = 2_147_483_647;
|
||||
|
|
@ -431,7 +430,7 @@ export function createBashToolDefinition(
|
|||
return createShellToolDefinition(cwd, bashToolConfig, options);
|
||||
}
|
||||
|
||||
export function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema> {
|
||||
export function createBashTool(cwd: string, options?: BashToolOptions): TypeBoxAgentTool<typeof bashSchema> {
|
||||
const definition = createBashToolDefinition(cwd, options);
|
||||
const tool = wrapToolDefinition(definition);
|
||||
Object.assign(tool, {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import { constants } from "fs";
|
||||
import { access as fsAccess, readFile as fsReadFile, writeFile as fsWriteFile } from "fs/promises";
|
||||
import { type Static, Type } from "typebox";
|
||||
|
|
@ -16,7 +15,7 @@ import {
|
|||
import { withFileMutationQueue } from "./file-mutation-queue.ts";
|
||||
import { resolveToCwd } from "./path-utils.ts";
|
||||
import { type EditRenderState, editRenderers } from "./renderers/edit.ts";
|
||||
import { wrapToolDefinition } from "./tool-definition-wrapper.ts";
|
||||
import { type TypeBoxAgentTool, wrapToolDefinition } from "./tool-definition-wrapper.ts";
|
||||
|
||||
const replaceEditSchema = Type.Object(
|
||||
{
|
||||
|
|
@ -213,6 +212,6 @@ export function createEditToolDefinition(
|
|||
};
|
||||
}
|
||||
|
||||
export function createEditTool(cwd: string, options?: EditToolOptions): AgentTool<typeof editSchema> {
|
||||
export function createEditTool(cwd: string, options?: EditToolOptions): TypeBoxAgentTool<typeof editSchema> {
|
||||
return wrapToolDefinition(createEditToolDefinition(cwd, options));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { createInterface } from "node:readline";
|
||||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import { spawn } from "child_process";
|
||||
import path from "path";
|
||||
import { type Static, Type } from "typebox";
|
||||
|
|
@ -7,7 +6,7 @@ import { ensureTool } from "../../utils/tools-manager.ts";
|
|||
import type { ExtensionContext, ToolDefinition } from "../extensions/types.ts";
|
||||
import { pathExists, resolveToCwd } from "./path-utils.ts";
|
||||
import { findRenderers } from "./renderers/find.ts";
|
||||
import { wrapToolDefinition } from "./tool-definition-wrapper.ts";
|
||||
import { type TypeBoxAgentTool, wrapToolDefinition } from "./tool-definition-wrapper.ts";
|
||||
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateHead } from "./truncate.ts";
|
||||
|
||||
/** Relativize a find result against the search root and normalize it to posix separators. */
|
||||
|
|
@ -313,6 +312,6 @@ export function createFindToolDefinition(
|
|||
};
|
||||
}
|
||||
|
||||
export function createFindTool(cwd: string, options?: FindToolOptions): AgentTool<typeof findSchema> {
|
||||
export function createFindTool(cwd: string, options?: FindToolOptions): TypeBoxAgentTool<typeof findSchema> {
|
||||
return wrapToolDefinition(createFindToolDefinition(cwd, options));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { readFile as fsReadFile, stat as fsStat } from "node:fs/promises";
|
||||
import { createInterface } from "node:readline";
|
||||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import { spawn } from "child_process";
|
||||
import path from "path";
|
||||
import { type Static, Type } from "typebox";
|
||||
|
|
@ -8,7 +7,7 @@ import { ensureTool } from "../../utils/tools-manager.ts";
|
|||
import type { ExtensionContext, ToolDefinition } from "../extensions/types.ts";
|
||||
import { resolveToCwd } from "./path-utils.ts";
|
||||
import { grepRenderers } from "./renderers/grep.ts";
|
||||
import { wrapToolDefinition } from "./tool-definition-wrapper.ts";
|
||||
import { type TypeBoxAgentTool, wrapToolDefinition } from "./tool-definition-wrapper.ts";
|
||||
import {
|
||||
DEFAULT_MAX_BYTES,
|
||||
formatSize,
|
||||
|
|
@ -320,6 +319,6 @@ export function createGrepToolDefinition(
|
|||
};
|
||||
}
|
||||
|
||||
export function createGrepTool(cwd: string, options?: GrepToolOptions): AgentTool<typeof grepSchema> {
|
||||
export function createGrepTool(cwd: string, options?: GrepToolOptions): TypeBoxAgentTool<typeof grepSchema> {
|
||||
return wrapToolDefinition(createGrepToolDefinition(cwd, options));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import { readdir as fsReaddir, stat as fsStat } from "node:fs/promises";
|
||||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import nodePath from "path";
|
||||
import { type Static, Type } from "typebox";
|
||||
import type { ExtensionContext, ToolDefinition } from "../extensions/types.ts";
|
||||
import { pathExists, resolveToCwd } from "./path-utils.ts";
|
||||
import { lsRenderers } from "./renderers/ls.ts";
|
||||
import { wrapToolDefinition } from "./tool-definition-wrapper.ts";
|
||||
import { type TypeBoxAgentTool, wrapToolDefinition } from "./tool-definition-wrapper.ts";
|
||||
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateHead } from "./truncate.ts";
|
||||
|
||||
const lsSchema = Type.Object({
|
||||
|
|
@ -170,6 +169,6 @@ export function createLsToolDefinition(
|
|||
};
|
||||
}
|
||||
|
||||
export function createLsTool(cwd: string, options?: LsToolOptions): AgentTool<typeof lsSchema> {
|
||||
export function createLsTool(cwd: string, options?: LsToolOptions): TypeBoxAgentTool<typeof lsSchema> {
|
||||
return wrapToolDefinition(createLsToolDefinition(cwd, options));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import type { Api, ImageContent, Model, ModelImageResizeOptions, TextContent } from "@earendil-works/pi-ai";
|
||||
import { constants } from "fs";
|
||||
import { access as fsAccess, readFile as fsReadFile } from "fs/promises";
|
||||
|
|
@ -8,7 +7,7 @@ import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime.ts";
|
|||
import type { ExtensionContext, ToolDefinition } from "../extensions/types.ts";
|
||||
import { resolveReadPathAsync } from "./path-utils.ts";
|
||||
import { readRenderers } from "./renderers/read.ts";
|
||||
import { wrapToolDefinition } from "./tool-definition-wrapper.ts";
|
||||
import { type TypeBoxAgentTool, wrapToolDefinition } from "./tool-definition-wrapper.ts";
|
||||
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateHead } from "./truncate.ts";
|
||||
|
||||
const readSchema = Type.Object({
|
||||
|
|
@ -219,6 +218,6 @@ export function createReadToolDefinition(
|
|||
};
|
||||
}
|
||||
|
||||
export function createReadTool(cwd: string, options?: ReadToolOptions): AgentTool<typeof readSchema> {
|
||||
export function createReadTool(cwd: string, options?: ReadToolOptions): TypeBoxAgentTool<typeof readSchema> {
|
||||
return wrapToolDefinition(createReadToolDefinition(cwd, options));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,25 @@
|
|||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import type { AgentTool, AgentToolResult, AgentToolUpdateCallback } from "@earendil-works/pi-agent-core";
|
||||
import type { Static, TSchema } from "typebox";
|
||||
import type { ExtensionToolContext, ToolDefinition } from "../extensions/types.ts";
|
||||
|
||||
/**
|
||||
* An `AgentTool` with a TypeBox parameter schema whose `execute` and `prepareArguments` are typed by TypeBox's
|
||||
* `Static`, as the built-in tools are (pi-agent-core types `params` through pi-schema, which gives `unknown` for
|
||||
* TypeBox schemas). Spreads into a `ToolDefinition` of the same schema.
|
||||
*/
|
||||
export type TypeBoxAgentTool<TParams extends TSchema = TSchema, TDetails = any> = Omit<
|
||||
AgentTool<TParams, TDetails>,
|
||||
"prepareArguments" | "execute"
|
||||
> & {
|
||||
prepareArguments?: (args: unknown) => Static<TParams>;
|
||||
execute: (
|
||||
toolCallId: string,
|
||||
params: Static<TParams>,
|
||||
signal?: AbortSignal,
|
||||
onUpdate?: AgentToolUpdateCallback<TDetails>,
|
||||
) => Promise<AgentToolResult<TDetails>>;
|
||||
};
|
||||
|
||||
/** Creates the context for one tool call. */
|
||||
export type ToolContextFactory = (toolCallId: string, signal: AbortSignal | undefined) => ExtensionToolContext;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import { mkdir as fsMkdir, writeFile as fsWriteFile } from "fs/promises";
|
||||
import { dirname } from "path";
|
||||
import { type Static, Type } from "typebox";
|
||||
|
|
@ -6,7 +5,7 @@ import type { ExtensionContext, ToolDefinition } from "../extensions/types.ts";
|
|||
import { withFileMutationQueue } from "./file-mutation-queue.ts";
|
||||
import { resolveToCwd } from "./path-utils.ts";
|
||||
import { writeRenderers } from "./renderers/write.ts";
|
||||
import { wrapToolDefinition } from "./tool-definition-wrapper.ts";
|
||||
import { type TypeBoxAgentTool, wrapToolDefinition } from "./tool-definition-wrapper.ts";
|
||||
|
||||
const writeSchema = Type.Object({
|
||||
path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
|
||||
|
|
@ -92,6 +91,6 @@ export function createWriteToolDefinition(
|
|||
};
|
||||
}
|
||||
|
||||
export function createWriteTool(cwd: string, options?: WriteToolOptions): AgentTool<typeof writeSchema> {
|
||||
export function createWriteTool(cwd: string, options?: WriteToolOptions): TypeBoxAgentTool<typeof writeSchema> {
|
||||
return wrapToolDefinition(createWriteToolDefinition(cwd, options));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import type { ToolDefinition, ToolLoadout, ToolLoadoutChanges, ToolNamespace } f
|
|||
import type { ModelRegistry } from "../../core/model-registry.ts";
|
||||
import { SETTINGS_DEFAULTS } from "../../core/settings-defaults.ts";
|
||||
import type { CodemodeMode } from "../../core/settings-manager.ts";
|
||||
import { wrapToolDefinition } from "../../core/tools/tool-definition-wrapper.ts";
|
||||
import { type TypeBoxAgentTool, wrapToolDefinition } from "../../core/tools/tool-definition-wrapper.ts";
|
||||
import { loadCodemodeExecutor } from "./execute.lazy.ts";
|
||||
import { codemodeRenderers } from "./renderer.ts";
|
||||
|
||||
|
|
@ -422,7 +422,7 @@ export function createCodemodeToolDefinition(
|
|||
export function createCodemodeTool(
|
||||
tools: readonly AgentTool<any>[] = [],
|
||||
options: CodemodeToolOptions = {},
|
||||
): AgentTool<typeof codemodeSchema> {
|
||||
): TypeBoxAgentTool<typeof codemodeSchema> {
|
||||
const definition = createCodemodeToolDefinition(options);
|
||||
const tool = wrapToolDefinition(definition);
|
||||
Object.assign(tool, {
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ import {
|
|||
getModel,
|
||||
type Model,
|
||||
type SimpleStreamOptions,
|
||||
Type,
|
||||
} from "@earendil-works/pi-ai/compat";
|
||||
import { Type } from "typebox";
|
||||
import {
|
||||
getOpenAICodexWebSocketDebugStats,
|
||||
streamSimple as streamSimpleOpenAICodexResponses,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,10 @@ import { Type } from "typebox";
|
|||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { ExtensionAPI } from "../../src/core/extensions/types.ts";
|
||||
import type { CustomEntry } from "../../src/core/session-manager.ts";
|
||||
import { createToolDefinitionFromAgentTool } from "../../src/core/tools/tool-definition-wrapper.ts";
|
||||
import {
|
||||
createToolDefinitionFromAgentTool,
|
||||
type TypeBoxAgentTool,
|
||||
} from "../../src/core/tools/tool-definition-wrapper.ts";
|
||||
import { readCodemodeStore } from "../../src/extensions/codemode/execute.ts";
|
||||
import { createCodemodeExtension } from "../../src/extensions/codemode/index.ts";
|
||||
import {
|
||||
|
|
@ -33,7 +36,7 @@ const TINY_PNG_BASE64 =
|
|||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==";
|
||||
|
||||
const echoSchema = Type.Object({ text: Type.String({ description: "Text to echo" }) });
|
||||
const echoTool: AgentTool<typeof echoSchema> = {
|
||||
const echoTool: TypeBoxAgentTool<typeof echoSchema> = {
|
||||
name: "echo",
|
||||
label: "Echo",
|
||||
description: "Echo text back.\n\nSecond paragraph.",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue