@plasm_lang/vercel-agent 0.3.63
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/README.md +235 -0
- package/agent/.plasm/archives/runs/runs/pr2d5c4a99707b4c19b650553d50229a1d600d28e3d98a9c58f18e5026cecc86ca.json +64 -0
- package/agent/.plasm/archives/runs/runs/pr2e0c0d8ad443c63c82da7435ee1a002b0e0fa718b640263c0a9d3e6e5944812f.json +64 -0
- package/agent/.plasm/archives/runs/runs/pr2faedb8354f40ee6d828e3af07b421fda9ccda973a4f7347fce3639f03a0a869.json +64 -0
- package/agent/.plasm/archives/runs/runs/pr586b47c55547b0702c572bce4255558b22dbe5e682d6359169577e0ea75fe98f.json +64 -0
- package/agent/.plasm/archives/runs/runs/pr76212356445e3b00fcf256835aaec18bac68576324b90d0be92d47f0b4a862a7.json +56 -0
- package/agent/.plasm/archives/runs/runs/pr9ec805d689e00db9270a9539858f2fb7216c24acbfea943d450e37b641149da1.json +64 -0
- package/agent/.plasm/archives/runs/runs/prc3c0c4ba2e28fc94ed6d37b6796e277a7997d9cb3184640d14c35c98bc6d136f.json +64 -0
- package/agent/.plasm/archives/runs/runs/prf04de32522f2fdcb17818907d91bccce7dcaecbd1259041cc448d447b6993244.json +64 -0
- package/agent/.plasm/archives/traces/traces/local/00000000000000000000000000000000/records.ndjson +1 -0
- package/agent/.plasm/archives/traces/traces/local/00000000000000000000000000000000/summary.json +13 -0
- package/agent/.plasm/discovery/manifest.json +126 -0
- package/agent/.plasm/sessions/TGlzdCBwcm9kdWN0cyBmcm9tIHRoZSBleGVjdXRlX3RpbnkgY2F0YWxvZw.json +44 -0
- package/agent/.plasm/sessions/TGlzdCBwcm9kdWN0cyBmcm9tIHRoZSBleGVjdXRlX3RpbnkgY2F0YWxvZw.teaching.tsv +23 -0
- package/agent/.plasm/sessions/bGlzdCBleGVjdXRlX3RpbnkgcHJvZHVjdHM.json +151 -0
- package/agent/.plasm/sessions/bGlzdCBleGVjdXRlX3RpbnkgcHJvZHVjdHM.teaching.tsv +131 -0
- package/agent/.plasm/sessions/bGlzdCBleGVjdXRlX3RpbnkgcHJvZHVjdHMgYXN5bmMgdHJhbnNwb3J0.json +44 -0
- package/agent/.plasm/sessions/bGlzdCBleGVjdXRlX3RpbnkgcHJvZHVjdHMgYXN5bmMgdHJhbnNwb3J0.teaching.tsv +23 -0
- package/agent/.plasm/stubs/.gitkeep +0 -0
- package/agent/.plasm/stubs/execute_tiny.ts +107 -0
- package/agent/agent.ts +52 -0
- package/agent/catalogs/README.md +15 -0
- package/agent/channels/.gitkeep +0 -0
- package/agent/channels/execute-tiny-webhook.ts +59 -0
- package/agent/channels/health.ts +16 -0
- package/agent/hooks/.gitkeep +0 -0
- package/agent/hooks/trace-log.ts +10 -0
- package/agent/instructions.md +25 -0
- package/agent/schedules/.gitkeep +0 -0
- package/agent/schedules/ping.ts +13 -0
- package/agent/skills/.gitkeep +0 -0
- package/agent/skills/plasm-authoring.md +8 -0
- package/agent/subagents/.gitkeep +0 -0
- package/agent/subagents/tiny/agent.ts +28 -0
- package/bin/plasm-agent.mjs +18 -0
- package/package.json +77 -0
- package/scripts/plasm-node.mjs +19 -0
- package/scripts/resolve-ts-extension.mjs +18 -0
- package/src/archive/adapters.ts +27 -0
- package/src/archive/index.ts +99 -0
- package/src/archive/local-run-archive.ts +90 -0
- package/src/archive/local-trace-archive.ts +91 -0
- package/src/archive/paths.ts +15 -0
- package/src/archive/postgres-kv-adapter.ts +72 -0
- package/src/archive/prod-archive-store.ts +143 -0
- package/src/archive/resolve-backend.ts +60 -0
- package/src/archive/run-id.ts +23 -0
- package/src/archive/types.ts +75 -0
- package/src/archive/vercel-blob-adapter.ts +21 -0
- package/src/archive/vercel-kv-adapter.ts +24 -0
- package/src/authoring/channel-dispatch.ts +44 -0
- package/src/authoring/context.ts +34 -0
- package/src/authoring/define-channel.ts +83 -0
- package/src/authoring/define-hook.ts +51 -0
- package/src/authoring/define-schedule.ts +64 -0
- package/src/authoring/define-skill.ts +38 -0
- package/src/authoring/hook-runner.ts +18 -0
- package/src/authoring/schedule-manager.ts +118 -0
- package/src/authoring/slot-loader.ts +253 -0
- package/src/authoring/subagent-loader.ts +121 -0
- package/src/catalog/loader.ts +71 -0
- package/src/cli/build.ts +54 -0
- package/src/cli/dev.ts +60 -0
- package/src/cli/info.ts +12 -0
- package/src/cli/init.ts +372 -0
- package/src/cli/link.ts +68 -0
- package/src/cli/project-root.ts +57 -0
- package/src/define-agent.ts +150 -0
- package/src/dev/client/ansi.ts +36 -0
- package/src/dev/client/http-session.ts +180 -0
- package/src/dev/client/repl.ts +92 -0
- package/src/dev/client/slash.ts +119 -0
- package/src/dev/dev-session.ts +153 -0
- package/src/dev/http.ts +29 -0
- package/src/dev/server.ts +147 -0
- package/src/dev/session-routes.ts +185 -0
- package/src/discovery/project-walker.ts +272 -0
- package/src/engine/connect-auth.ts +135 -0
- package/src/engine/create-host-transport.ts +7 -0
- package/src/engine/fixture-mock-transport.ts +54 -0
- package/src/engine/host-transport-bridge.ts +32 -0
- package/src/engine/host-transport.ts +84 -0
- package/src/engine/napi-binding.ts +265 -0
- package/src/evals/define-eval.ts +56 -0
- package/src/evals/run-eval.ts +136 -0
- package/src/gateway-model.ts +43 -0
- package/src/index.ts +296 -0
- package/src/instrumentation.ts +56 -0
- package/src/load-env.ts +63 -0
- package/src/operator/routes.ts +287 -0
- package/src/operator/types.ts +63 -0
- package/src/operator/ui-shell.ts +134 -0
- package/src/project-info.ts +229 -0
- package/src/runtime/agent-runtime.ts +469 -0
- package/src/runtime/compaction.ts +81 -0
- package/src/runtime/logical-session.ts +72 -0
- package/src/runtime/plasm-agent.ts +199 -0
- package/src/server/plasm-handler.ts +331 -0
- package/src/session-state.ts +135 -0
- package/src/state/define-state.ts +57 -0
- package/src/state/fs-state-adapter.ts +72 -0
- package/src/state/kv-state-adapter.ts +62 -0
- package/src/state/postgres-state-adapter.ts +116 -0
- package/src/stubs/capability-invoke-shape.ts +135 -0
- package/src/stubs/catalog-client.ts +170 -0
- package/src/stubs/catalog-hash.ts +11 -0
- package/src/stubs/catalog-introspection.ts +121 -0
- package/src/stubs/cgs-ts-types.ts +164 -0
- package/src/stubs/domain-parser.ts +203 -0
- package/src/stubs/generator.ts +390 -0
- package/src/stubs/input-type-to-ts.ts +233 -0
- package/src/stubs/plasm-value-emitter.ts +162 -0
- package/src/stubs/program-builder.ts +82 -0
- package/src/stubs/stub-symbols.ts +89 -0
- package/src/symbol-registry.ts +74 -0
- package/src/telemetry/plasm-spans.ts +83 -0
- package/src/tools/descriptions.ts +94 -0
- package/src/tools/format.ts +29 -0
- package/src/tools/harness-tools.ts +65 -0
- package/src/tools/plasm-tools.ts +104 -0
- package/src/workflow/world-bootstrap.ts +52 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plasm engine binding — in-process NAPI (`@plasm_lang/engine`) with stub fallback.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import type { LoadedCatalog } from "../catalog/loader.js";
|
|
7
|
+
import { toNapiHostTransport } from "./host-transport-bridge.js";
|
|
8
|
+
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
|
|
11
|
+
export interface TeachingExposureResult {
|
|
12
|
+
tsv: string;
|
|
13
|
+
deltaRefs: string[];
|
|
14
|
+
executeSessionRef?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface DryRunResult {
|
|
18
|
+
planCommitRef: string;
|
|
19
|
+
summary: string;
|
|
20
|
+
compJson?: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ResolvedPlanPayload {
|
|
24
|
+
/** application/vnd.plasm.resolved-plan+json */
|
|
25
|
+
planJson: unknown;
|
|
26
|
+
catalogCgsHash: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Host-injected outbound HTTP transport (Connect bearer, etc.). */
|
|
30
|
+
export type HostTransportRequest = {
|
|
31
|
+
method: string;
|
|
32
|
+
url: string;
|
|
33
|
+
headers?: Record<string, string>;
|
|
34
|
+
body?: string;
|
|
35
|
+
entryId?: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type HostTransportResponse = {
|
|
39
|
+
status: number;
|
|
40
|
+
body: string;
|
|
41
|
+
nextUrl?: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type HostTransportFn = (request: HostTransportRequest) => Promise<HostTransportResponse>;
|
|
45
|
+
|
|
46
|
+
export interface PlasmEngine {
|
|
47
|
+
loadCatalog(catalog: LoadedCatalog): Promise<void>;
|
|
48
|
+
synthesizeTeaching(
|
|
49
|
+
intent: string,
|
|
50
|
+
seeds: Array<{ api: string; entity: string }>,
|
|
51
|
+
): Promise<TeachingExposureResult>;
|
|
52
|
+
dryRun(program: string, executeSessionRef?: string): Promise<DryRunResult>;
|
|
53
|
+
discover(intent: string): Promise<{ markdown: string }>;
|
|
54
|
+
runPlan(planCommitRef: string): Promise<{ ok: boolean; message: string; rowsJson?: string; metaJson?: string }>;
|
|
55
|
+
runPlanLive?(
|
|
56
|
+
planCommitRef: string,
|
|
57
|
+
transport: HostTransportFn,
|
|
58
|
+
): Promise<{ ok: boolean; message: string; rowsJson?: string; metaJson?: string }>;
|
|
59
|
+
run(resolved: ResolvedPlanPayload, transport: HostTransportFn): Promise<unknown>;
|
|
60
|
+
introspectCatalog(entryId: string): Promise<string>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type NativePlasmEngine = {
|
|
64
|
+
loadCatalog(catalogDir: string): {
|
|
65
|
+
entryId: string;
|
|
66
|
+
catalogCgsHash: string;
|
|
67
|
+
};
|
|
68
|
+
exposeSeeds(
|
|
69
|
+
intent: string,
|
|
70
|
+
seeds: Array<{ api: string; entity: string }>,
|
|
71
|
+
): {
|
|
72
|
+
tsv: string;
|
|
73
|
+
deltaRefs: string[];
|
|
74
|
+
};
|
|
75
|
+
dryRun(program: string): {
|
|
76
|
+
planCommitRef: string;
|
|
77
|
+
summary: string;
|
|
78
|
+
compJson: string;
|
|
79
|
+
};
|
|
80
|
+
discover(intent: string): {
|
|
81
|
+
markdown: string;
|
|
82
|
+
};
|
|
83
|
+
runPlan(planCommitRef: string): {
|
|
84
|
+
ok: boolean;
|
|
85
|
+
message: string;
|
|
86
|
+
rowsJson?: string;
|
|
87
|
+
metaJson?: string;
|
|
88
|
+
};
|
|
89
|
+
runPlanLive(
|
|
90
|
+
planCommitRef: string,
|
|
91
|
+
transport: (request: HostTransportRequest) => Promise<HostTransportResponse>,
|
|
92
|
+
): Promise<{
|
|
93
|
+
ok: boolean;
|
|
94
|
+
message: string;
|
|
95
|
+
rowsJson?: string;
|
|
96
|
+
metaJson?: string;
|
|
97
|
+
}>;
|
|
98
|
+
introspectCatalog(entryId: string): string;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
type NativeConstructor = new () => NativePlasmEngine;
|
|
102
|
+
|
|
103
|
+
function loadNativeConstructor(): NativeConstructor | null {
|
|
104
|
+
try {
|
|
105
|
+
const mod = require("@plasm_lang/engine") as { PlasmEngine: NativeConstructor };
|
|
106
|
+
return mod.PlasmEngine ?? null;
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** NAPI-backed engine (`@plasm_lang/engine`). Build with `npm run build` in `packages/plasm-engine`. */
|
|
113
|
+
export class NapiPlasmEngine implements PlasmEngine {
|
|
114
|
+
private readonly native: NativePlasmEngine;
|
|
115
|
+
private loaded: LoadedCatalog[] = [];
|
|
116
|
+
|
|
117
|
+
constructor(NativeEngine: NativeConstructor = loadNativeConstructor()!) {
|
|
118
|
+
if (!NativeEngine) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
"@plasm_lang/engine native binding not found — run `npm run build` in packages/plasm-engine",
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
this.native = new NativeEngine();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async loadCatalog(catalog: LoadedCatalog): Promise<void> {
|
|
127
|
+
const info = this.native.loadCatalog(catalog.rootDir);
|
|
128
|
+
this.loaded.push({
|
|
129
|
+
...catalog,
|
|
130
|
+
manifest: {
|
|
131
|
+
...catalog.manifest,
|
|
132
|
+
entryId: info.entryId,
|
|
133
|
+
cgsHash: info.catalogCgsHash,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async synthesizeTeaching(
|
|
139
|
+
intent: string,
|
|
140
|
+
seeds: Array<{ api: string; entity: string }>,
|
|
141
|
+
): Promise<TeachingExposureResult> {
|
|
142
|
+
void this.loaded;
|
|
143
|
+
const result = this.native.exposeSeeds(intent, seeds);
|
|
144
|
+
return {
|
|
145
|
+
tsv: result.tsv,
|
|
146
|
+
deltaRefs: result.deltaRefs,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async dryRun(program: string, executeSessionRef?: string): Promise<DryRunResult> {
|
|
151
|
+
void executeSessionRef;
|
|
152
|
+
const result = this.native.dryRun(program);
|
|
153
|
+
let compJson: unknown;
|
|
154
|
+
try {
|
|
155
|
+
compJson = JSON.parse(result.compJson) as unknown;
|
|
156
|
+
} catch {
|
|
157
|
+
compJson = result.compJson;
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
planCommitRef: result.planCommitRef,
|
|
161
|
+
summary: result.summary,
|
|
162
|
+
compJson,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async discover(intent: string): Promise<{ markdown: string }> {
|
|
167
|
+
return this.native.discover(intent);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async runPlan(planCommitRef: string): Promise<{ ok: boolean; message: string; rowsJson?: string }> {
|
|
171
|
+
return this.native.runPlan(planCommitRef);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async runPlanLive(
|
|
175
|
+
planCommitRef: string,
|
|
176
|
+
transport: HostTransportFn,
|
|
177
|
+
): Promise<{ ok: boolean; message: string; rowsJson?: string; metaJson?: string }> {
|
|
178
|
+
return this.native.runPlanLive(planCommitRef, toNapiHostTransport(transport));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async run(_resolved: ResolvedPlanPayload, transport: HostTransportFn): Promise<unknown> {
|
|
182
|
+
void _resolved;
|
|
183
|
+
throw new Error(
|
|
184
|
+
"NapiPlasmEngine.run(resolvedPlan): pass planCommitRef via runPlanLive(pcN, transport) instead",
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async introspectCatalog(entryId: string): Promise<string> {
|
|
189
|
+
return this.native.introspectCatalog(entryId);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Stub until `@plasm_lang/engine` is built or on unsupported platforms. */
|
|
194
|
+
export class StubPlasmEngine implements PlasmEngine {
|
|
195
|
+
private loaded: LoadedCatalog[] = [];
|
|
196
|
+
|
|
197
|
+
async loadCatalog(catalog: LoadedCatalog): Promise<void> {
|
|
198
|
+
this.loaded.push(catalog);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async synthesizeTeaching(
|
|
202
|
+
intent: string,
|
|
203
|
+
seeds: Array<{ api: string; entity: string }>,
|
|
204
|
+
): Promise<TeachingExposureResult> {
|
|
205
|
+
void intent;
|
|
206
|
+
return {
|
|
207
|
+
tsv: `# teaching TSV placeholder\n# seeds: ${JSON.stringify(seeds)}`,
|
|
208
|
+
deltaRefs: [],
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async dryRun(program: string, executeSessionRef?: string): Promise<DryRunResult> {
|
|
213
|
+
void executeSessionRef;
|
|
214
|
+
return {
|
|
215
|
+
planCommitRef: "pc0",
|
|
216
|
+
summary: `[stub dry-run] ${program.slice(0, 80)}`,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async discover(intent: string): Promise<{ markdown: string }> {
|
|
221
|
+
return {
|
|
222
|
+
markdown: `# Discovery stub\n\nIntent: ${intent}\n\nLoad @plasm_lang/engine native binding for real discovery TSV.`,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async runPlan(planCommitRef: string): Promise<{ ok: boolean; message: string; rowsJson?: string }> {
|
|
227
|
+
return {
|
|
228
|
+
ok: false,
|
|
229
|
+
message: `[stub] plan ${planCommitRef} — wire NAPI binding`,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async runPlanLive(
|
|
234
|
+
planCommitRef: string,
|
|
235
|
+
_transport: HostTransportFn,
|
|
236
|
+
): Promise<{ ok: boolean; message: string; rowsJson?: string }> {
|
|
237
|
+
return {
|
|
238
|
+
ok: false,
|
|
239
|
+
message: `[stub] live run ${planCommitRef} — build @plasm_lang/engine`,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async run(_resolved: ResolvedPlanPayload, _transport: HostTransportFn): Promise<unknown> {
|
|
244
|
+
throw new Error("StubPlasmEngine.run: wire NAPI binding or plasm-server sidecar");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async introspectCatalog(_entryId: string): Promise<string> {
|
|
248
|
+
throw new Error("StubPlasmEngine.introspectCatalog: build @plasm_lang/engine");
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function createEngine(mode: "napi" | "stub" = "napi"): PlasmEngine {
|
|
253
|
+
if (mode === "stub") {
|
|
254
|
+
return new StubPlasmEngine();
|
|
255
|
+
}
|
|
256
|
+
const Native = loadNativeConstructor();
|
|
257
|
+
if (Native) {
|
|
258
|
+
return new NapiPlasmEngine(Native);
|
|
259
|
+
}
|
|
260
|
+
return new StubPlasmEngine();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function isNativeEngineAvailable(): boolean {
|
|
264
|
+
return loadNativeConstructor() !== null;
|
|
265
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const PLASM_EVAL_KIND = "eval" as const;
|
|
2
|
+
|
|
3
|
+
export type EvalToolName =
|
|
4
|
+
| "discover_capabilities"
|
|
5
|
+
| "plasm_context"
|
|
6
|
+
| "plasm"
|
|
7
|
+
| "plasm_run"
|
|
8
|
+
| "read_skill"
|
|
9
|
+
| "delegate_subagent";
|
|
10
|
+
|
|
11
|
+
export interface EvalAssert {
|
|
12
|
+
/** Tools that must appear in at least one step (live agent loop). */
|
|
13
|
+
toolsUsedAny?: EvalToolName[];
|
|
14
|
+
/** Final assistant text must match. */
|
|
15
|
+
responseIncludes?: string | RegExp;
|
|
16
|
+
/** Minimum tool-calling steps observed. */
|
|
17
|
+
minSteps?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface EvalDefinition {
|
|
21
|
+
readonly __plasmSlotKind: typeof PLASM_EVAL_KIND;
|
|
22
|
+
name: string;
|
|
23
|
+
/** Natural-language goal sent to the live agent loop. */
|
|
24
|
+
goal: string;
|
|
25
|
+
assert: EvalAssert;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface DefineEvalInput {
|
|
29
|
+
name: string;
|
|
30
|
+
goal: string;
|
|
31
|
+
assert: EvalAssert;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Live LLM eval — requires AI Gateway; fixture-only chains are not supported. */
|
|
35
|
+
export function defineEval(input: DefineEvalInput): EvalDefinition {
|
|
36
|
+
if (!input.name?.trim()) {
|
|
37
|
+
throw new Error("defineEval: name is required");
|
|
38
|
+
}
|
|
39
|
+
if (!input.goal?.trim()) {
|
|
40
|
+
throw new Error("defineEval: goal is required");
|
|
41
|
+
}
|
|
42
|
+
return Object.freeze({
|
|
43
|
+
__plasmSlotKind: PLASM_EVAL_KIND,
|
|
44
|
+
name: input.name.trim(),
|
|
45
|
+
goal: input.goal.trim(),
|
|
46
|
+
assert: input.assert,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function isEvalDefinition(value: unknown): value is EvalDefinition {
|
|
51
|
+
return (
|
|
52
|
+
typeof value === "object" &&
|
|
53
|
+
value !== null &&
|
|
54
|
+
(value as EvalDefinition).__plasmSlotKind === PLASM_EVAL_KIND
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
import type { PlasmAgent } from "../runtime/plasm-agent.js";
|
|
6
|
+
import type { EvalDefinition, EvalToolName } from "./define-eval.js";
|
|
7
|
+
import { isEvalDefinition } from "./define-eval.js";
|
|
8
|
+
|
|
9
|
+
export interface EvalRunResult {
|
|
10
|
+
name: string;
|
|
11
|
+
ok: boolean;
|
|
12
|
+
recordedTools: EvalToolName[];
|
|
13
|
+
responseText?: string;
|
|
14
|
+
stepCount: number;
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function collectToolNames(steps: unknown[]): EvalToolName[] {
|
|
19
|
+
const names = new Set<EvalToolName>();
|
|
20
|
+
for (const step of steps) {
|
|
21
|
+
if (!step || typeof step !== "object") continue;
|
|
22
|
+
const toolCalls = (step as { toolCalls?: Array<{ toolName?: string }> }).toolCalls ?? [];
|
|
23
|
+
for (const call of toolCalls) {
|
|
24
|
+
const name = call.toolName;
|
|
25
|
+
if (name) names.add(name as EvalToolName);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return [...names];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function importEvalModule(filePath: string): Promise<EvalDefinition> {
|
|
32
|
+
const mod = await import(pathToFileURL(filePath).href);
|
|
33
|
+
const exported = mod.default ?? mod;
|
|
34
|
+
if (!isEvalDefinition(exported)) {
|
|
35
|
+
throw new Error(`default export must be defineEval() result`);
|
|
36
|
+
}
|
|
37
|
+
return exported;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function discoverEvalFiles(evalsDir: string): Promise<string[]> {
|
|
41
|
+
const entries = await readdir(evalsDir, { withFileTypes: true });
|
|
42
|
+
return entries
|
|
43
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".eval.ts"))
|
|
44
|
+
.map((entry) => path.join(evalsDir, entry.name))
|
|
45
|
+
.sort((a, b) => a.localeCompare(b));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Run one live LLM eval via agent.generate(). */
|
|
49
|
+
export async function runEval(
|
|
50
|
+
agent: PlasmAgent,
|
|
51
|
+
definition: EvalDefinition,
|
|
52
|
+
): Promise<EvalRunResult> {
|
|
53
|
+
let recordedTools: EvalToolName[] = [];
|
|
54
|
+
let responseText: string | undefined;
|
|
55
|
+
let stepCount = 0;
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const result = await agent.generate(definition.goal, { resetConversation: true });
|
|
59
|
+
responseText = result.text;
|
|
60
|
+
stepCount = result.steps.length;
|
|
61
|
+
recordedTools = collectToolNames(result.steps);
|
|
62
|
+
|
|
63
|
+
if (definition.assert.minSteps !== undefined && stepCount < definition.assert.minSteps) {
|
|
64
|
+
throw new Error(`expected at least ${definition.assert.minSteps} steps, got ${stepCount}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (definition.assert.toolsUsedAny?.length) {
|
|
68
|
+
for (const tool of definition.assert.toolsUsedAny) {
|
|
69
|
+
if (!recordedTools.includes(tool)) {
|
|
70
|
+
throw new Error(`expected tool ${tool} in live run, got [${recordedTools.join(", ")}]`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (definition.assert.responseIncludes) {
|
|
76
|
+
const needle = definition.assert.responseIncludes;
|
|
77
|
+
const haystack = responseText ?? "";
|
|
78
|
+
const matches =
|
|
79
|
+
typeof needle === "string" ? haystack.includes(needle) : needle.test(haystack);
|
|
80
|
+
if (!matches) {
|
|
81
|
+
throw new Error(`response missing expected fragment`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
name: definition.name,
|
|
87
|
+
ok: true,
|
|
88
|
+
recordedTools,
|
|
89
|
+
responseText,
|
|
90
|
+
stepCount,
|
|
91
|
+
};
|
|
92
|
+
} catch (err) {
|
|
93
|
+
return {
|
|
94
|
+
name: definition.name,
|
|
95
|
+
ok: false,
|
|
96
|
+
recordedTools,
|
|
97
|
+
responseText,
|
|
98
|
+
stepCount,
|
|
99
|
+
error: String(err),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function runEvalFile(
|
|
105
|
+
agent: PlasmAgent,
|
|
106
|
+
evalPath: string,
|
|
107
|
+
): Promise<EvalRunResult> {
|
|
108
|
+
const definition = await importEvalModule(evalPath);
|
|
109
|
+
return runEval(agent, definition);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function requireLiveEvalGateway(): void {
|
|
113
|
+
const key =
|
|
114
|
+
process.env.AI_GATEWAY_API_KEY?.trim() ||
|
|
115
|
+
process.env.AI_API_GATEWAY_KEY?.trim() ||
|
|
116
|
+
process.env.AI_GATEWAY_KEY?.trim();
|
|
117
|
+
if (!key) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
"Live evals require AI_GATEWAY_API_KEY (Vercel AI Gateway). Fixture-only eval chains are not supported.",
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
process.env.AI_GATEWAY_API_KEY ??= key;
|
|
123
|
+
process.env.PLASM_AGENT_MOCK_HTTP = "1";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function runAllEvals(
|
|
127
|
+
agent: PlasmAgent,
|
|
128
|
+
evalsDir: string,
|
|
129
|
+
): Promise<EvalRunResult[]> {
|
|
130
|
+
const files = await discoverEvalFiles(evalsDir);
|
|
131
|
+
const results: EvalRunResult[] = [];
|
|
132
|
+
for (const file of files) {
|
|
133
|
+
results.push(await runEvalFile(agent, file));
|
|
134
|
+
}
|
|
135
|
+
return results;
|
|
136
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { gateway, type LanguageModel } from "ai";
|
|
2
|
+
|
|
3
|
+
import type { AgentModelOptions } from "./define-agent.js";
|
|
4
|
+
|
|
5
|
+
function ensureGatewayApiKey(): void {
|
|
6
|
+
if (process.env.AI_GATEWAY_API_KEY?.trim()) return;
|
|
7
|
+
const alias =
|
|
8
|
+
process.env.AI_API_GATEWAY_KEY?.trim() ?? process.env.AI_GATEWAY_KEY?.trim();
|
|
9
|
+
if (alias) {
|
|
10
|
+
process.env.AI_GATEWAY_API_KEY = alias;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Whether `resolveGatewayModel` can run (API key or mapped alias present). */
|
|
15
|
+
export function isGatewayConfigured(): boolean {
|
|
16
|
+
ensureGatewayApiKey();
|
|
17
|
+
return Boolean(process.env.AI_GATEWAY_API_KEY?.trim());
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Resolve a Gateway model slug (`provider/model`) to an AI SDK `LanguageModel`. */
|
|
21
|
+
export function resolveGatewayModel(
|
|
22
|
+
model: string | LanguageModel,
|
|
23
|
+
_options?: AgentModelOptions,
|
|
24
|
+
): LanguageModel {
|
|
25
|
+
if (typeof model !== "string") return model;
|
|
26
|
+
const slug = model.trim();
|
|
27
|
+
if (!slug) {
|
|
28
|
+
throw new Error("agent `model` must be a non-empty AI Gateway slug");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
ensureGatewayApiKey();
|
|
32
|
+
if (!process.env.AI_GATEWAY_API_KEY?.trim()) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
[
|
|
35
|
+
"Vercel AI Gateway requires AI_GATEWAY_API_KEY.",
|
|
36
|
+
"Create one in the Vercel dashboard (AI → Gateway → API Keys) and add it to plasm-oss/.env.",
|
|
37
|
+
"Local dev: `vercel env pull` from a linked project also works.",
|
|
38
|
+
].join(" "),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return gateway(slug);
|
|
43
|
+
}
|