okengine 0.13.0 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/manifest.v1.schema.json +7 -1
- package/package.json +4 -5
- package/site/content/docs/ai/mcp.mdx +27 -2
- package/site/content/docs/elements/ai.mdx +59 -11
- package/site/content/docs/elements/channel.mdx +1 -0
- package/site/content/docs/elements/clock.mdx +30 -12
- package/site/content/docs/elements/flow.mdx +9 -3
- package/site/content/docs/elements/gate.mdx +25 -22
- package/site/content/docs/elements/signal.mdx +71 -25
- package/site/content/docs/elements/store.mdx +87 -28
- package/site/content/docs/elements/vault.mdx +1 -0
- package/site/content/docs/get-started/basic-usage.mdx +22 -13
- package/site/content/docs/get-started/index.mdx +5 -0
- package/site/content/docs/get-started/installation.mdx +2 -2
- package/site/content/docs/get-started/introduction.mdx +11 -3
- package/site/content/docs/get-started/meta.json +1 -1
- package/site/content/docs/get-started/project-structure.mdx +932 -0
- package/site/content/docs/reference/cli.md +13 -10
- package/site/content/docs/reference/client.mdx +25 -21
- package/site/content/docs/reference/fx.mdx +27 -22
- package/site/content/docs/reference/security.md +1 -1
- package/src/auth/bindings.ts +14 -5
- package/src/auth/cross-plane.ts +10 -3
- package/src/cli/ai-setup/ai-setup.test.ts +62 -0
- package/src/cli/ai-setup/apply.ts +32 -1
- package/src/cli/ai-setup/recommend.test.ts +25 -0
- package/src/cli/ai-setup/recommend.ts +8 -3
- package/src/cli/attach-host-console.test.ts +3 -2
- package/src/cli/attach-host-console.ts +3 -0
- package/src/cli/build.test.ts +4 -2
- package/src/cli/db-seed.ts +48 -15
- package/src/cli/db.test.ts +104 -0
- package/src/cli/db.ts +122 -10
- package/src/cli/dev-controls.test.ts +20 -0
- package/src/cli/dev-controls.ts +13 -0
- package/src/cli/dev.test.ts +15 -4
- package/src/cli/dev.ts +102 -29
- package/src/cli/ensure-drizzle-config.ts +2 -1
- package/src/cli/tui/DevLive.tsx +12 -2
- package/src/cli/tui/keys.test.ts +2 -1
- package/src/cli/tui/keys.ts +3 -2
- package/src/compiler/aot.test.ts +1 -1
- package/src/compiler/effects-infer.ts +58 -3
- package/src/compiler/extract.test.ts +458 -29
- package/src/compiler/extract.ts +387 -69
- package/src/compiler/flow-path.test.ts +96 -0
- package/src/compiler/flow-path.ts +173 -0
- package/src/compiler/generate-adopt.test.ts +112 -3
- package/src/compiler/generate-adopt.ts +280 -26
- package/src/compiler/response.ts +45 -1
- package/src/compiler/schema-from-ast.ts +609 -0
- package/src/console/server/ai.ts +5 -2
- package/src/console/server/bind.ts +2 -2
- package/src/console/server/console.test.ts +3 -0
- package/src/console/server/flows.ts +121 -40
- package/src/console/server/invoke-user-flow.test.ts +3 -1
- package/src/console/server/invoke-user-flow.ts +17 -2
- package/src/console/server/iso-at.test.ts +9 -0
- package/src/console/server/iso-at.ts +11 -0
- package/src/console/server/security-headers.ts +6 -3
- package/src/console/server/serve.ts +17 -2
- package/src/console/server/spa-proxy.test.ts +1 -0
- package/src/console/server/sql-catalog.ts +147 -7
- package/src/console/server/sql-rls.test.ts +16 -0
- package/src/console/server/state.ts +8 -0
- package/src/console/server/store.test.ts +7 -4
- package/src/console/server/store.ts +142 -58
- package/src/console/ui-next/dist/assets/agent-disclosure-C0X1fbWF.js +1 -0
- package/src/console/ui-next/dist/assets/cache-glyph-F1FI122b.js +1 -0
- package/src/console/ui-next/dist/assets/{call-pii-button-bqkxMrJH.js → call-pii-button-DEDCl_j3.js} +1 -1
- package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +1 -0
- package/src/console/ui-next/dist/assets/{confirm-sheet-DDCRmG62.js → confirm-sheet-3ptDzXbz.js} +1 -1
- package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
- package/src/console/ui-next/dist/assets/{explorer-empty-HjTnVQoR.js → explorer-empty-BvYrygyO.js} +1 -1
- package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-CS_O8L-r.js} +1 -1
- package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +1 -0
- package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +66 -0
- package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +2 -0
- package/src/console/ui-next/dist/assets/{link-DF7SZ9Ek.js → link-COeyggt-.js} +1 -1
- package/src/console/ui-next/dist/assets/observability-page-HK9-BO8z.js +4 -0
- package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
- package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +18 -0
- package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +1 -0
- package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +1 -0
- package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +54 -0
- package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-BLbZhzyF.js} +1 -1
- package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +2 -0
- package/src/console/ui-next/dist/index.html +6 -6
- package/src/console/ui-next/seed-invoke-host.ts +2 -0
- package/src/console/ui-next/src/client.ts +35 -7
- package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
- package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
- package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
- package/src/console/ui-next/src/features/flows/graph/build-flow-graph.test.ts +18 -0
- package/src/console/ui-next/src/features/flows/graph/build-flow-graph.ts +14 -1
- package/src/console/ui-next/src/features/flows/graph/neighborhood.test.ts +17 -0
- package/src/console/ui-next/src/features/flows/graph/neighborhood.ts +16 -3
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +3 -1
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +9 -3
- package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +29 -2
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
- package/src/console/ui-next/src/features/observability/lib/ask-count.test.ts +25 -0
- package/src/console/ui-next/src/features/observability/lib/ask-count.ts +4 -1
- package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
- package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
- package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
- package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
- package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
- package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
- package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
- package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
- package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
- package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
- package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
- package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
- package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
- package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
- package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
- package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
- package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
- package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
- package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
- package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
- package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +12 -4
- package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
- package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
- package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
- package/src/docker/compose-health.test.ts +27 -0
- package/src/docker/compose-health.ts +19 -3
- package/src/docker/docker.test.ts +4 -1
- package/src/docker/dockerfile.ts +1 -1
- package/src/docker/recipes/pgdog.ts +2 -0
- package/src/drivers/ai-anthropic.ts +5 -0
- package/src/drivers/ai-ollama.ts +49 -30
- package/src/drivers/ai-openai-compatible.ts +57 -46
- package/src/drivers/ai-providers.test.ts +3 -0
- package/src/drivers/bun-native-completeness.test.ts +7 -9
- package/src/drivers/clock-postgres.ts +13 -9
- package/src/drivers/instances-postgres.ts +4 -6
- package/src/drivers/journal-postgres.ts +13 -9
- package/src/drivers/pg-rls.test.ts +87 -0
- package/src/drivers/pg-rls.ts +233 -7
- package/src/drivers/pglite.ts +26 -3
- package/src/drivers/postgres.test.ts +23 -0
- package/src/drivers/postgres.ts +133 -9
- package/src/drivers/redis.ts +11 -4
- package/src/drivers/signal-redis.ts +24 -14
- package/src/drivers/signal-types.ts +2 -1
- package/src/drivers/types.ts +10 -0
- package/src/elements/ai/declare.ts +109 -0
- package/src/elements/ai/errors.test.ts +5 -1
- package/src/elements/ai/errors.ts +30 -2
- package/src/elements/ai/eval.ts +4 -6
- package/src/elements/ai/mcp-client.test.ts +206 -0
- package/src/elements/ai/mcp-client.ts +362 -0
- package/src/elements/ai/mcp-http.ts +159 -0
- package/src/elements/ai/mcp-mock.ts +134 -0
- package/src/elements/ai/mcp-protocol.ts +234 -0
- package/src/elements/ai/mcp-stdio.test.ts +50 -0
- package/src/elements/ai/mcp-stdio.ts +212 -0
- package/src/elements/ai/mcp-transport.ts +70 -0
- package/src/elements/ai/runtime.ts +159 -29
- package/src/elements/ai.test.ts +139 -0
- package/src/elements/ai.ts +16 -0
- package/src/elements/clock/declare.ts +26 -1
- package/src/elements/clock/health.test.ts +43 -0
- package/src/elements/clock/runtime.ts +55 -2
- package/src/elements/clock/schedule.ts +71 -142
- package/src/elements/clock.test.ts +9 -40
- package/src/elements/clock.ts +6 -1
- package/src/elements/gate/boot.ts +5 -5
- package/src/elements/gate/declare.ts +36 -4
- package/src/elements/gate/flatten.ts +1 -1
- package/src/elements/gate/runtime.ts +1 -1
- package/src/elements/index.ts +2 -0
- package/src/elements/signal/declare.ts +2 -1
- package/src/elements/signal/runtime.ts +16 -1
- package/src/elements/signal.ts +1 -0
- package/src/elements/store/cache.test.ts +2 -0
- package/src/elements/store/cache.ts +3 -3
- package/src/elements/store/declare.ts +9 -6
- package/src/elements/store/emit-drizzle.ts +44 -8
- package/src/elements/store/resource.test.ts +1 -1
- package/src/elements/store/rls-identity.test.ts +95 -0
- package/src/elements/store/rls-identity.ts +116 -0
- package/src/elements/store/runtime.ts +4 -0
- package/src/elements/store/schema-decl.test.ts +37 -0
- package/src/elements/store/schema-decl.ts +161 -0
- package/src/elements/store/seed.ts +1 -1
- package/src/elements/store/sql-condition.test.ts +12 -0
- package/src/elements/store/sql-condition.ts +26 -2
- package/src/elements/store/sql-rls-isolation.test.ts +165 -0
- package/src/elements/store/sql-rls-stamp.test.ts +117 -0
- package/src/elements/store/sql-session.ts +81 -3
- package/src/elements/store/upsert-app.test.ts +1 -1
- package/src/elements/store.ts +14 -0
- package/src/elements/vault/builtin-adapter.ts +17 -0
- package/src/full.ts +2 -0
- package/src/http.ts +20 -3
- package/src/index.ts +14 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
- package/src/kernel/adopt-routes.ts +30 -2
- package/src/kernel/app.ts +227 -77
- package/src/kernel/auto-cache.test.ts +8 -8
- package/src/kernel/auto-registry.test.ts +54 -9
- package/src/kernel/boot-bind/ai.ts +24 -0
- package/src/kernel/boot-bind/clock.ts +2 -0
- package/src/kernel/boot.test.ts +4 -4
- package/src/kernel/boot.ts +5 -3
- package/src/kernel/effects-stamping.test.ts +3 -3
- package/src/kernel/element-registries.ts +15 -5
- package/src/kernel/errors.ts +24 -0
- package/src/kernel/flow-units.ts +64 -0
- package/src/kernel/flow.ts +29 -17
- package/src/kernel/fx-dead-letters.test.ts +77 -0
- package/src/kernel/fx.test.ts +22 -0
- package/src/kernel/fx.ts +135 -6
- package/src/kernel/horizontal-child.ts +4 -4
- package/src/kernel/http-path-pending.ts +20 -0
- package/src/kernel/http-resource.ts +4 -0
- package/src/kernel/http-stream.test.ts +174 -0
- package/src/kernel/index.ts +2 -0
- package/src/kernel/pipeline.test.ts +2 -2
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/plugin-needs.test.ts +1 -1
- package/src/kernel/ready.test.ts +2 -2
- package/src/kernel/resource-mount.test.ts +14 -0
- package/src/kernel/run-duration.test.ts +2 -2
- package/src/kernel/stamp-http.test.ts +79 -0
- package/src/kernel/stamp-http.ts +75 -0
- package/src/kernel/triggers.ts +52 -27
- package/src/manifest/mcp-ref.ts +88 -0
- package/src/manifest/types.ts +41 -2
- package/src/manifest/validate.test.ts +20 -0
- package/src/mcp/docs-server.ts +1 -1
- package/src/mcp/server.ts +1 -1
- package/src/plugins/compression.test.ts +21 -0
- package/src/plugins/compression.ts +1 -0
- package/src/release/measure.ts +2 -2
- package/src/runtime/bun.ts +41 -4
- package/src/runtime/json-code-block.test.ts +2 -2
- package/src/runtime/serve.test.ts +4 -4
- package/src/term.test.ts +52 -4
- package/src/term.ts +125 -17
- package/src/test/reset-element-registries.ts +10 -3
- package/src/cli/ask-seed.test.ts +0 -96
- package/src/cli/ask-seed.ts +0 -82
- package/src/console/ui-next/dist/assets/cache-glyph-CLPBqZeb.js +0 -1
- package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-Dg8CTE29.js +0 -1
- package/src/console/ui-next/dist/assets/http-method-BJ92Z_ke.js +0 -1
- package/src/console/ui-next/dist/assets/index-Bp-R7jtM.js +0 -66
- package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
- package/src/console/ui-next/dist/assets/observability-page-DAnpEaq1.js +0 -4
- package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
- package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
- package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
- package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
- package/src/console/ui-next/dist/assets/skeleton-CL_X0GCj.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
- package/src/console/ui-next/dist/assets/trace-detail-sheet-D16lWQMt.js +0 -2
- package/src/console/ui-next/dist/assets/units-page-BtQ0bqMe.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-Ca-MvcmJ.js +0 -2
package/src/drivers/types.ts
CHANGED
|
@@ -62,6 +62,16 @@ export interface SqlConnection {
|
|
|
62
62
|
* @param params - Bound parameters
|
|
63
63
|
*/
|
|
64
64
|
exec(sql: string, params?: readonly unknown[]): Promise<{ changes: number }>;
|
|
65
|
+
/**
|
|
66
|
+
* Pin `fn` to one backend connection for a single transaction.
|
|
67
|
+
*
|
|
68
|
+
* Required for `SET LOCAL` RLS stamps on pooled `postgres` (Bun.SQL /
|
|
69
|
+
* PgDog transaction mode). A standalone `BEGIN` via {@link exec} returns
|
|
70
|
+
* the connection to the pool still in a transaction and exhausts checkout.
|
|
71
|
+
*
|
|
72
|
+
* @param fn - Work that must share the reserved connection
|
|
73
|
+
*/
|
|
74
|
+
transaction?<T>(fn: (tx: SqlConnection) => Promise<T>): Promise<T>;
|
|
65
75
|
/** Close the connection. */
|
|
66
76
|
close(): Promise<void>;
|
|
67
77
|
}
|
|
@@ -7,9 +7,12 @@
|
|
|
7
7
|
import {
|
|
8
8
|
aiAgentRegistry,
|
|
9
9
|
aiEmbedRegistry,
|
|
10
|
+
aiMcpServerRegistry,
|
|
10
11
|
aiModelRegistry,
|
|
11
12
|
aiPromptRegistry,
|
|
12
13
|
} from "../../kernel/element-registries.ts";
|
|
14
|
+
import { mcpToolRef, type McpToolRef } from "../../manifest/mcp-ref.ts";
|
|
15
|
+
import type { VaultSecretDecl } from "../vault/declare.ts";
|
|
13
16
|
|
|
14
17
|
/** Budget for a prompt or agent. */
|
|
15
18
|
export interface AiBudgetDecl {
|
|
@@ -30,6 +33,11 @@ export interface AiModelOptions {
|
|
|
30
33
|
readonly baseUrl?: string;
|
|
31
34
|
/** Optional API key override for this binding (cloud providers). */
|
|
32
35
|
readonly apiKey?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Protocol driver for this binding (`anthropic`, `ollama`, …).
|
|
38
|
+
* When omitted, the app-level default driver is used.
|
|
39
|
+
*/
|
|
40
|
+
readonly driverId?: string;
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
/**
|
|
@@ -77,6 +85,7 @@ export interface AiModelDecl {
|
|
|
77
85
|
readonly model?: string;
|
|
78
86
|
readonly baseUrl?: string;
|
|
79
87
|
readonly apiKey?: string;
|
|
88
|
+
readonly driverId?: string;
|
|
80
89
|
/**
|
|
81
90
|
* Declare a versioned prompt artifact on this model.
|
|
82
91
|
*
|
|
@@ -118,6 +127,54 @@ export interface AiAgentDecl {
|
|
|
118
127
|
readonly budget?: AiBudgetDecl;
|
|
119
128
|
}
|
|
120
129
|
|
|
130
|
+
/** Bearer auth for {@link ai.mcpServer} — secret contract, never a token literal. */
|
|
131
|
+
export interface AiMcpServerAuthOptions {
|
|
132
|
+
readonly bearer: VaultSecretDecl | string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Options for {@link ai.mcpServer}. */
|
|
136
|
+
export interface AiMcpServerOptions {
|
|
137
|
+
/** Streamable HTTP endpoint. */
|
|
138
|
+
readonly url?: string;
|
|
139
|
+
/** stdio executable (no shell string). */
|
|
140
|
+
readonly command?: string;
|
|
141
|
+
/** Arguments for {@link command}. */
|
|
142
|
+
readonly args?: readonly string[];
|
|
143
|
+
/** Bearer secret contract (`vault.secret` handle or name). */
|
|
144
|
+
readonly auth?: AiMcpServerAuthOptions;
|
|
145
|
+
/**
|
|
146
|
+
* Required allowlist of tool names on this server.
|
|
147
|
+
* The runtime never offers whatever `tools/list` happens to expose.
|
|
148
|
+
*/
|
|
149
|
+
readonly tools: readonly string[];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Named capability ref returned by {@link AiMcpServerDecl.tool}. */
|
|
153
|
+
export interface AiMcpToolRef {
|
|
154
|
+
readonly name: McpToolRef;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Declared external MCP server — tools join `fx.call` / `toolLoop` as
|
|
159
|
+
* `mcp:<server>/<tool>`.
|
|
160
|
+
*/
|
|
161
|
+
export interface AiMcpServerDecl {
|
|
162
|
+
readonly kind: "mcp-server";
|
|
163
|
+
readonly name: string;
|
|
164
|
+
readonly url?: string;
|
|
165
|
+
readonly command?: string;
|
|
166
|
+
readonly args?: readonly string[];
|
|
167
|
+
/** Secret contract name when bearer auth is declared. */
|
|
168
|
+
readonly auth?: string;
|
|
169
|
+
readonly tools: readonly string[];
|
|
170
|
+
/**
|
|
171
|
+
* Capability ref for one allowlisted tool (`mcp:<server>/<tool>`).
|
|
172
|
+
*
|
|
173
|
+
* @param tool - Tool name on this server
|
|
174
|
+
*/
|
|
175
|
+
tool(tool: string): AiMcpToolRef;
|
|
176
|
+
}
|
|
177
|
+
|
|
121
178
|
/**
|
|
122
179
|
* Resolve a tool ref to a flow name.
|
|
123
180
|
*
|
|
@@ -152,6 +209,13 @@ export interface AiNamespace {
|
|
|
152
209
|
* @param options - Tools / maxSteps / model / budget
|
|
153
210
|
*/
|
|
154
211
|
agent(name: string, options?: AiAgentOptions): AiAgentDecl;
|
|
212
|
+
/**
|
|
213
|
+
* Declare an external MCP server whose allowlisted tools join `fx.call`.
|
|
214
|
+
*
|
|
215
|
+
* @param name - Server id (`github`, `linear`, …)
|
|
216
|
+
* @param options - Transport + required tool allowlist
|
|
217
|
+
*/
|
|
218
|
+
mcpServer(name: string, options: AiMcpServerOptions): AiMcpServerDecl;
|
|
155
219
|
}
|
|
156
220
|
|
|
157
221
|
/**
|
|
@@ -162,12 +226,14 @@ export function listAiDecls(): {
|
|
|
162
226
|
readonly prompts: readonly AiPromptDecl[];
|
|
163
227
|
readonly embeds: readonly AiEmbedDecl[];
|
|
164
228
|
readonly agents: readonly AiAgentDecl[];
|
|
229
|
+
readonly mcpServers: readonly AiMcpServerDecl[];
|
|
165
230
|
} {
|
|
166
231
|
return {
|
|
167
232
|
models: aiModelRegistry.slice(),
|
|
168
233
|
prompts: aiPromptRegistry.slice(),
|
|
169
234
|
embeds: aiEmbedRegistry.slice(),
|
|
170
235
|
agents: aiAgentRegistry.slice(),
|
|
236
|
+
mcpServers: aiMcpServerRegistry.slice(),
|
|
171
237
|
};
|
|
172
238
|
}
|
|
173
239
|
|
|
@@ -179,6 +245,7 @@ export function resetAiDecls(): void {
|
|
|
179
245
|
aiPromptRegistry.length = 0;
|
|
180
246
|
aiEmbedRegistry.length = 0;
|
|
181
247
|
aiAgentRegistry.length = 0;
|
|
248
|
+
aiMcpServerRegistry.length = 0;
|
|
182
249
|
}
|
|
183
250
|
|
|
184
251
|
/**
|
|
@@ -201,6 +268,7 @@ export const ai: AiNamespace = {
|
|
|
201
268
|
...(options.model !== undefined ? { model: options.model } : {}),
|
|
202
269
|
...(options.baseUrl !== undefined ? { baseUrl: options.baseUrl } : {}),
|
|
203
270
|
...(options.apiKey !== undefined ? { apiKey: options.apiKey } : {}),
|
|
271
|
+
...(options.driverId !== undefined ? { driverId: options.driverId } : {}),
|
|
204
272
|
prompt(promptName, promptOpts = {}) {
|
|
205
273
|
const promptDecl: AiPromptDecl = {
|
|
206
274
|
kind: "prompt",
|
|
@@ -263,4 +331,45 @@ export const ai: AiNamespace = {
|
|
|
263
331
|
aiAgentRegistry.push(decl);
|
|
264
332
|
return decl;
|
|
265
333
|
},
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Declare an external MCP server whose allowlisted tools join `fx.call`.
|
|
337
|
+
*
|
|
338
|
+
* @param name - Server id
|
|
339
|
+
* @param options - Transport + required tool allowlist
|
|
340
|
+
*/
|
|
341
|
+
mcpServer(name: string, options: AiMcpServerOptions): AiMcpServerDecl {
|
|
342
|
+
if (!name) throw new TypeError("ai.mcpServer: name is required");
|
|
343
|
+
if (name.includes("/") || name.includes("__")) {
|
|
344
|
+
throw new TypeError(`ai.mcpServer: name "${name}" must not contain "/" or "__"`);
|
|
345
|
+
}
|
|
346
|
+
if (!options.tools || !Array.isArray(options.tools)) {
|
|
347
|
+
throw new TypeError("ai.mcpServer: tools allowlist is required");
|
|
348
|
+
}
|
|
349
|
+
const hasUrl = typeof options.url === "string" && options.url.length > 0;
|
|
350
|
+
const hasCommand = typeof options.command === "string" && options.command.length > 0;
|
|
351
|
+
if (hasUrl === hasCommand) {
|
|
352
|
+
throw new TypeError("ai.mcpServer: declare exactly one of url or command");
|
|
353
|
+
}
|
|
354
|
+
const allow = new Set(options.tools);
|
|
355
|
+
const authName =
|
|
356
|
+
typeof options.auth?.bearer === "string" ? options.auth.bearer : options.auth?.bearer?.name;
|
|
357
|
+
const decl: AiMcpServerDecl = {
|
|
358
|
+
kind: "mcp-server",
|
|
359
|
+
name,
|
|
360
|
+
...(hasUrl ? { url: options.url } : {}),
|
|
361
|
+
...(hasCommand ? { command: options.command } : {}),
|
|
362
|
+
...(options.args !== undefined ? { args: options.args } : {}),
|
|
363
|
+
...(authName !== undefined ? { auth: authName } : {}),
|
|
364
|
+
tools: options.tools,
|
|
365
|
+
tool(tool: string): AiMcpToolRef {
|
|
366
|
+
if (!allow.has(tool)) {
|
|
367
|
+
throw new TypeError(`ai.mcpServer("${name}"): tool "${tool}" is not in the allowlist`);
|
|
368
|
+
}
|
|
369
|
+
return { name: mcpToolRef(name, tool) };
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
aiMcpServerRegistry.push(decl);
|
|
373
|
+
return decl;
|
|
374
|
+
},
|
|
266
375
|
};
|
|
@@ -22,7 +22,11 @@ describe("isRetryableAiError", () => {
|
|
|
22
22
|
expect(isRetryableAiError(new Error("openai-compatible HTTP 401"))).toBe(false);
|
|
23
23
|
const abort = new Error("aborted");
|
|
24
24
|
abort.name = "AbortError";
|
|
25
|
-
expect(isRetryableAiError(abort)).toBe(
|
|
25
|
+
expect(isRetryableAiError(abort)).toBe(false);
|
|
26
|
+
const timeout = new Error("deadline");
|
|
27
|
+
timeout.name = "TimeoutError";
|
|
28
|
+
expect(isRetryableAiError(timeout)).toBe(true);
|
|
29
|
+
expect(isRetryableAiError(new Error("request timeout"))).toBe(true);
|
|
26
30
|
});
|
|
27
31
|
});
|
|
28
32
|
|
|
@@ -74,8 +74,10 @@ export function isRetryableAiError(err: unknown): boolean {
|
|
|
74
74
|
if (code >= 400 && code <= 499) return false;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
if (name === "
|
|
78
|
-
|
|
77
|
+
if (name === "TimeoutError" || isTimeoutAbort(err)) return true;
|
|
78
|
+
// Client disconnect / explicit cancel must not retry or advance `via`.
|
|
79
|
+
if (name === "AbortError") return false;
|
|
80
|
+
if (name === "AiBudgetExceededError") return false;
|
|
79
81
|
if (
|
|
80
82
|
lower.includes("econnreset") ||
|
|
81
83
|
lower.includes("econnrefused") ||
|
|
@@ -103,6 +105,32 @@ export function aiHttpError(message: string, status: number): Error {
|
|
|
103
105
|
return err;
|
|
104
106
|
}
|
|
105
107
|
|
|
108
|
+
/**
|
|
109
|
+
* True when an abort came from a deadline (`AbortSignal.timeout`), not a
|
|
110
|
+
* client disconnect / explicit cancel.
|
|
111
|
+
*
|
|
112
|
+
* @param err - Thrown value
|
|
113
|
+
*/
|
|
114
|
+
function isTimeoutAbort(err: unknown): boolean {
|
|
115
|
+
if (err == null || typeof err !== "object") return false;
|
|
116
|
+
const name = err instanceof Error ? err.name : "";
|
|
117
|
+
const message = err instanceof Error ? err.message.toLowerCase() : "";
|
|
118
|
+
if (name === "TimeoutError") return true;
|
|
119
|
+
if (message.includes("timeout")) return true;
|
|
120
|
+
const cause = "cause" in err ? (err as { cause: unknown }).cause : undefined;
|
|
121
|
+
if (cause instanceof Error && (cause.name === "TimeoutError" || /timeout/i.test(cause.message))) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
const reason = "reason" in err ? (err as { reason: unknown }).reason : undefined;
|
|
125
|
+
if (
|
|
126
|
+
reason instanceof Error &&
|
|
127
|
+
(reason.name === "TimeoutError" || /timeout/i.test(reason.message))
|
|
128
|
+
) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
106
134
|
function readStatus(err: unknown): number | undefined {
|
|
107
135
|
if (typeof err !== "object" || err === null) return undefined;
|
|
108
136
|
const status = (err as AiErrorFields).status;
|
package/src/elements/ai/eval.ts
CHANGED
|
@@ -124,11 +124,9 @@ export async function runPromptEvals(options: RunPromptEvalsOptions): Promise<Ev
|
|
|
124
124
|
* @param text - File text
|
|
125
125
|
*/
|
|
126
126
|
export function parseEvalJsonl(text: string): EvalCase[] {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (!trimmed) continue;
|
|
131
|
-
cases.push(JSON.parse(trimmed) as EvalCase);
|
|
127
|
+
const jsonl = (Bun as typeof Bun & { JSONL?: { parse: (t: string) => unknown[] } }).JSONL;
|
|
128
|
+
if (!jsonl) {
|
|
129
|
+
throw new Error("ai.eval: Bun.JSONL is required (Bun >= 1.4.0)");
|
|
132
130
|
}
|
|
133
|
-
return
|
|
131
|
+
return jsonl.parse(text) as EvalCase[];
|
|
134
132
|
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP client — allowlist, era fallback, abort, capability.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import { createMockAiDriver } from "../../drivers/ai-mock.ts";
|
|
7
|
+
import { createFx } from "../../kernel/fx.ts";
|
|
8
|
+
import {
|
|
9
|
+
mcpCapabilityRefFromName,
|
|
10
|
+
mcpModelToolName,
|
|
11
|
+
parseMcpToolRef,
|
|
12
|
+
} from "../../manifest/mcp-ref.ts";
|
|
13
|
+
import { ai } from "./declare.ts";
|
|
14
|
+
import { createMcpClient } from "./mcp-client.ts";
|
|
15
|
+
import { createMockMcpTransport } from "./mcp-mock.ts";
|
|
16
|
+
import { createAiRuntime } from "./runtime.ts";
|
|
17
|
+
|
|
18
|
+
describe("mcp refs", () => {
|
|
19
|
+
test("parses capability and model names", () => {
|
|
20
|
+
expect(parseMcpToolRef("mcp:github/create_issue")).toEqual({
|
|
21
|
+
server: "github",
|
|
22
|
+
tool: "create_issue",
|
|
23
|
+
});
|
|
24
|
+
expect(mcpModelToolName("github", "create_issue")).toBe("github__create_issue");
|
|
25
|
+
expect(mcpCapabilityRefFromName("github__create_issue")).toBe("mcp:github/create_issue");
|
|
26
|
+
expect(parseMcpToolRef("github/create_issue")).toBeNull();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("mcp client allowlist", () => {
|
|
31
|
+
test("drops extras from tools/list and calls only allowlisted tools", async () => {
|
|
32
|
+
const github = ai.mcpServer("github", {
|
|
33
|
+
url: "https://mcp.example/github",
|
|
34
|
+
tools: ["create_issue"],
|
|
35
|
+
});
|
|
36
|
+
const called: string[] = [];
|
|
37
|
+
const transport = createMockMcpTransport({
|
|
38
|
+
tools: [
|
|
39
|
+
{ name: "create_issue", description: "Open an issue" },
|
|
40
|
+
{ name: "delete_repo", description: "should be dropped" },
|
|
41
|
+
],
|
|
42
|
+
extraTools: [{ name: "admin_wipe" }],
|
|
43
|
+
onCall: (name) => {
|
|
44
|
+
called.push(name);
|
|
45
|
+
return { content: [{ type: "text", text: "opened" }] };
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
const client = createMcpClient({
|
|
49
|
+
servers: [github],
|
|
50
|
+
transports: { github: transport },
|
|
51
|
+
});
|
|
52
|
+
const listed = await client.listedTool({ server: "github", tool: "create_issue" });
|
|
53
|
+
expect(listed?.name).toBe("create_issue");
|
|
54
|
+
expect(await client.listedTool({ server: "github", tool: "delete_repo" })).toBeUndefined();
|
|
55
|
+
await client.call("mcp:github/create_issue", { title: "bug" });
|
|
56
|
+
expect(called).toEqual(["create_issue"]);
|
|
57
|
+
await expect(client.call("mcp:github/delete_repo", {})).rejects.toThrow(/not allowlisted/);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("mcp era fallback", () => {
|
|
62
|
+
test("400 + UnsupportedProtocolVersion stays modern (no initialize)", async () => {
|
|
63
|
+
const methods: string[] = [];
|
|
64
|
+
const github = ai.mcpServer("github", {
|
|
65
|
+
url: "https://mcp.example/github",
|
|
66
|
+
tools: ["create_issue"],
|
|
67
|
+
});
|
|
68
|
+
const transport = createMockMcpTransport({
|
|
69
|
+
tools: [{ name: "create_issue" }],
|
|
70
|
+
httpStatusOnFirst: 400,
|
|
71
|
+
firstError: { code: -32022, message: "UnsupportedProtocolVersion" },
|
|
72
|
+
});
|
|
73
|
+
const wrapped = {
|
|
74
|
+
kind: "mock" as const,
|
|
75
|
+
async request(req: Parameters<typeof transport.request>[0]) {
|
|
76
|
+
methods.push(req.method);
|
|
77
|
+
return transport.request(req);
|
|
78
|
+
},
|
|
79
|
+
close: () => transport.close(),
|
|
80
|
+
};
|
|
81
|
+
const client = createMcpClient({
|
|
82
|
+
servers: [github],
|
|
83
|
+
transports: { github: wrapped },
|
|
84
|
+
});
|
|
85
|
+
await expect(client.listedTool({ server: "github", tool: "create_issue" })).rejects.toThrow(
|
|
86
|
+
/UnsupportedProtocolVersion/,
|
|
87
|
+
);
|
|
88
|
+
expect(methods).toEqual(["tools/list"]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("400 without a modern body falls back to initialize", async () => {
|
|
92
|
+
const methods: string[] = [];
|
|
93
|
+
const github = ai.mcpServer("github", {
|
|
94
|
+
url: "https://mcp.example/github",
|
|
95
|
+
tools: ["create_issue"],
|
|
96
|
+
});
|
|
97
|
+
const transport = createMockMcpTransport({
|
|
98
|
+
era: "legacy",
|
|
99
|
+
tools: [{ name: "create_issue" }],
|
|
100
|
+
httpStatusOnFirst: 400,
|
|
101
|
+
firstBody: "bad request",
|
|
102
|
+
});
|
|
103
|
+
const wrapped = {
|
|
104
|
+
kind: "mock" as const,
|
|
105
|
+
async request(req: Parameters<typeof transport.request>[0]) {
|
|
106
|
+
methods.push(req.method);
|
|
107
|
+
return transport.request(req);
|
|
108
|
+
},
|
|
109
|
+
close: () => transport.close(),
|
|
110
|
+
};
|
|
111
|
+
const client = createMcpClient({
|
|
112
|
+
servers: [github],
|
|
113
|
+
transports: { github: wrapped },
|
|
114
|
+
});
|
|
115
|
+
const listed = await client.listedTool({ server: "github", tool: "create_issue" });
|
|
116
|
+
expect(listed?.name).toBe("create_issue");
|
|
117
|
+
expect(methods).toEqual(["tools/list", "initialize", "tools/list"]);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("mcp abort", () => {
|
|
122
|
+
test("HTTP cancel aborts an in-flight mock request", async () => {
|
|
123
|
+
const github = ai.mcpServer("github", {
|
|
124
|
+
url: "https://mcp.example/github",
|
|
125
|
+
tools: ["create_issue"],
|
|
126
|
+
});
|
|
127
|
+
const transport = createMockMcpTransport({
|
|
128
|
+
tools: [{ name: "create_issue" }],
|
|
129
|
+
delayMs: 5_000,
|
|
130
|
+
});
|
|
131
|
+
const client = createMcpClient({
|
|
132
|
+
servers: [github],
|
|
133
|
+
transports: { github: transport },
|
|
134
|
+
});
|
|
135
|
+
const ctrl = new AbortController();
|
|
136
|
+
const pending = client.call("mcp:github/create_issue", {}, ctrl.signal);
|
|
137
|
+
ctrl.abort();
|
|
138
|
+
await expect(pending).rejects.toMatchObject({ name: "AbortError" });
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("mcp capability + toolLoop", () => {
|
|
143
|
+
test("undeclared MCP tool throws OKE1007", async () => {
|
|
144
|
+
const github = ai.mcpServer("github", {
|
|
145
|
+
url: "https://mcp.example/github",
|
|
146
|
+
tools: ["create_issue"],
|
|
147
|
+
});
|
|
148
|
+
const runtime = createAiRuntime({
|
|
149
|
+
mcpServers: [github],
|
|
150
|
+
mcpTransports: { github: createMockMcpTransport({ tools: [{ name: "create_issue" }] }) },
|
|
151
|
+
});
|
|
152
|
+
const fx = createFx({
|
|
153
|
+
flow: "support.triage",
|
|
154
|
+
effects: { asks: ["triage"] },
|
|
155
|
+
aiRuntime: runtime,
|
|
156
|
+
});
|
|
157
|
+
try {
|
|
158
|
+
await fx.call(github.tool("create_issue"), { title: "x" });
|
|
159
|
+
throw new Error("expected OKE1007");
|
|
160
|
+
} catch (err) {
|
|
161
|
+
expect(err).toMatchObject({ code: 1007 });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("model-facing server__tool dispatches through fx.call as mcp: ref", async () => {
|
|
166
|
+
const github = ai.mcpServer("github", {
|
|
167
|
+
url: "https://mcp.example/github",
|
|
168
|
+
tools: ["create_issue"],
|
|
169
|
+
});
|
|
170
|
+
const called: string[] = [];
|
|
171
|
+
const runtime = createAiRuntime({
|
|
172
|
+
models: [ai.model("smart")],
|
|
173
|
+
prompts: [ai.model("smart").prompt("triage")],
|
|
174
|
+
mcpServers: [github],
|
|
175
|
+
mcpTransports: {
|
|
176
|
+
github: createMockMcpTransport({
|
|
177
|
+
tools: [{ name: "create_issue", description: "Open an issue" }],
|
|
178
|
+
onCall: (name) => {
|
|
179
|
+
called.push(name);
|
|
180
|
+
return { content: [{ type: "text", text: "ok" }] };
|
|
181
|
+
},
|
|
182
|
+
}),
|
|
183
|
+
},
|
|
184
|
+
defaultDriver: createMockAiDriver({
|
|
185
|
+
"*": {
|
|
186
|
+
__toolCalls: [{ id: "c1", name: "github__create_issue", arguments: { title: "bug" } }],
|
|
187
|
+
},
|
|
188
|
+
}),
|
|
189
|
+
});
|
|
190
|
+
const fx = createFx({
|
|
191
|
+
flow: "support.triage",
|
|
192
|
+
effects: { asks: ["triage"], calls: ["mcp:github/create_issue"] },
|
|
193
|
+
aiRuntime: runtime,
|
|
194
|
+
});
|
|
195
|
+
await runtime.ask(
|
|
196
|
+
"triage",
|
|
197
|
+
{ q: "open" },
|
|
198
|
+
{
|
|
199
|
+
tools: ["mcp:github/create_issue"],
|
|
200
|
+
maxSteps: 1,
|
|
201
|
+
callTool: (name, input) => fx.call(name, input),
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
expect(called).toEqual(["create_issue"]);
|
|
205
|
+
});
|
|
206
|
+
});
|