@telnyx/agent-harness 0.1.0-beta.0
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 +179 -0
- package/dist/approvals.d.ts +140 -0
- package/dist/approvals.js +699 -0
- package/dist/channel.d.ts +178 -0
- package/dist/channel.js +184 -0
- package/dist/contract.d.ts +50 -0
- package/dist/contract.js +278 -0
- package/dist/durable.d.ts +81 -0
- package/dist/durable.js +650 -0
- package/dist/harness.d.ts +199 -0
- package/dist/harness.js +1223 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +19 -0
- package/dist/lifecycle.d.ts +9 -0
- package/dist/lifecycle.js +25 -0
- package/dist/node-adapter.d.ts +71 -0
- package/dist/node-adapter.js +736 -0
- package/dist/ports.d.ts +99 -0
- package/dist/ports.js +3 -0
- package/dist/runtime-adapter.d.ts +15 -0
- package/dist/runtime-adapter.js +70 -0
- package/dist/runtime-config.d.ts +38 -0
- package/dist/runtime-config.js +104 -0
- package/dist/scheduling.d.ts +46 -0
- package/dist/scheduling.js +425 -0
- package/dist/steps.d.ts +52 -0
- package/dist/steps.js +310 -0
- package/dist/tool-context.d.ts +8 -0
- package/dist/tool-context.js +9 -0
- package/dist/workspace.d.ts +151 -0
- package/dist/workspace.js +404 -0
- package/package.json +58 -0
package/dist/steps.js
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { ensureHarnessDurabilitySchema, HarnessInjectedCrash } from "./durable.js";
|
|
3
|
+
const MAX_STEP_NAME_BYTES = 512;
|
|
4
|
+
const MAX_VERSION_BYTES = 512;
|
|
5
|
+
const TOOL_OUTPUT_SERIALIZATION_FAILURE = "tool_output_serialization_failure";
|
|
6
|
+
/** Private durable marker that never shares the tool-output value channel. */
|
|
7
|
+
export class HarnessStepOutputSerializationError extends Error {
|
|
8
|
+
constructor() {
|
|
9
|
+
super("external step output is not JSON-safe");
|
|
10
|
+
this.name = "HarnessStepOutputSerializationError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
// Step contexts are reconstructed freely within one ports activation. Keep the
|
|
14
|
+
// live-effect claim with that activation, not with an individual context; a
|
|
15
|
+
// reopened ports instance intentionally starts fresh so durable crash recovery
|
|
16
|
+
// can replay a left-behind prepared row.
|
|
17
|
+
const activeStepsByActivation = new WeakMap();
|
|
18
|
+
function first(rows) { return rows[0]; }
|
|
19
|
+
function boundedNonblank(value, name, limit) {
|
|
20
|
+
if (!value.trim())
|
|
21
|
+
throw new Error(`${name} must not be blank`);
|
|
22
|
+
if (Buffer.byteLength(value, "utf8") > limit)
|
|
23
|
+
throw new Error(`${name} exceeds ${limit} UTF-8 bytes`);
|
|
24
|
+
}
|
|
25
|
+
function canonicalJson(value, seen = new WeakSet()) {
|
|
26
|
+
if (value === null || typeof value === "boolean" || typeof value === "string")
|
|
27
|
+
return JSON.stringify(value);
|
|
28
|
+
if (typeof value === "number") {
|
|
29
|
+
if (!Number.isFinite(value))
|
|
30
|
+
throw new Error("step input must be JSON-safe");
|
|
31
|
+
return JSON.stringify(value);
|
|
32
|
+
}
|
|
33
|
+
if (Array.isArray(value)) {
|
|
34
|
+
if (seen.has(value))
|
|
35
|
+
throw new Error("step input must be JSON-safe");
|
|
36
|
+
seen.add(value);
|
|
37
|
+
try {
|
|
38
|
+
const values = [];
|
|
39
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
40
|
+
if (!Object.hasOwn(value, index))
|
|
41
|
+
throw new Error("step input must be JSON-safe");
|
|
42
|
+
values.push(canonicalJson(value[index], seen));
|
|
43
|
+
}
|
|
44
|
+
return `[${values.join(",")}]`;
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
seen.delete(value);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (typeof value === "object") {
|
|
51
|
+
if (Object.getPrototypeOf(value) !== Object.prototype)
|
|
52
|
+
throw new Error("step input must be JSON-safe");
|
|
53
|
+
if (seen.has(value))
|
|
54
|
+
throw new Error("step input must be JSON-safe");
|
|
55
|
+
seen.add(value);
|
|
56
|
+
const record = value;
|
|
57
|
+
try {
|
|
58
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key], seen)}`).join(",")}}`;
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
seen.delete(value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
throw new Error("step input must be JSON-safe");
|
|
65
|
+
}
|
|
66
|
+
export function isToolOutputSerializationFailure(value) {
|
|
67
|
+
return value instanceof HarnessStepOutputSerializationError;
|
|
68
|
+
}
|
|
69
|
+
function serializeStepOutput(value, external) {
|
|
70
|
+
try {
|
|
71
|
+
return { output: canonicalJson(external && value === undefined ? null : value), failure: null };
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (!external)
|
|
75
|
+
throw error;
|
|
76
|
+
return { output: null, failure: TOOL_OUTPUT_SERIALIZATION_FAILURE };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function fingerprint(value) {
|
|
80
|
+
return createHash("sha256").update(value).digest("hex");
|
|
81
|
+
}
|
|
82
|
+
function checkpoint(callback, point) {
|
|
83
|
+
try {
|
|
84
|
+
callback?.(point);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
throw new HarnessInjectedCrash(error);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export function createHarnessStepContext(ports, options) {
|
|
91
|
+
boundedNonblank(options.runId, "run id", MAX_STEP_NAME_BYTES);
|
|
92
|
+
boundedNonblank(options.version, "step version", MAX_VERSION_BYTES);
|
|
93
|
+
const defaultInputFingerprint = fingerprint(canonicalJson(options.input));
|
|
94
|
+
const readRun = () => first(ports.sql.exec("SELECT status FROM __telnyx_agent_harness_runs WHERE id = ?", options.runId).toArray());
|
|
95
|
+
const readStep = (name) => first(ports.sql.exec("SELECT run_id,name,identity,input_fingerprint,version,status,output,output_failure,journal_seq FROM __telnyx_agent_harness_steps WHERE run_id = ? AND name = ?", options.runId, name).toArray());
|
|
96
|
+
let activeSteps = activeStepsByActivation.get(ports.activation);
|
|
97
|
+
if (activeSteps === undefined) {
|
|
98
|
+
activeSteps = new Map();
|
|
99
|
+
activeStepsByActivation.set(ports.activation, activeSteps);
|
|
100
|
+
}
|
|
101
|
+
const claimedToolCallOrdinals = new Set();
|
|
102
|
+
// Each pending reservation carries the provider toolCallId (when available)
|
|
103
|
+
// so associateToolCallJournal can match by identity instead of position.
|
|
104
|
+
// The AI SDK 6.0.266 executes tools concurrently via Promise.all, so
|
|
105
|
+
// execution-start order can diverge from model-emission order in step.toolCalls.
|
|
106
|
+
const pendingToolCalls = [];
|
|
107
|
+
const pendingByToolCallId = new Map();
|
|
108
|
+
let journaledToolCallMarkers = new Set();
|
|
109
|
+
const markerFor = (toolName, ordinal) => fingerprint(`${options.runId}\u0000${toolName}\u0000${ordinal}`);
|
|
110
|
+
const toolCallOrdinal = (toolName, toolCallId) => {
|
|
111
|
+
boundedNonblank(toolName, "tool name", MAX_STEP_NAME_BYTES);
|
|
112
|
+
if (toolCallId !== undefined) {
|
|
113
|
+
const existing = pendingByToolCallId.get(toolCallId);
|
|
114
|
+
if (existing !== undefined) {
|
|
115
|
+
if (existing.toolName !== toolName)
|
|
116
|
+
throw new Error("durable tool call journal does not match executed calls");
|
|
117
|
+
return existing.ordinal;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
ensureHarnessDurabilitySchema(ports);
|
|
121
|
+
const rows = ports.sql.exec("SELECT ordinal,journal_marker FROM __telnyx_agent_harness_tool_calls WHERE run_id = ? AND tool_name = ? ORDER BY ordinal", options.runId, toolName).toArray();
|
|
122
|
+
const pending = rows.find((row) => !claimedToolCallOrdinals.has(`${toolName}\u0000${row.ordinal}`)
|
|
123
|
+
&& (row.journal_marker === null || !journaledToolCallMarkers.has(row.journal_marker)));
|
|
124
|
+
if (pending !== undefined) {
|
|
125
|
+
claimedToolCallOrdinals.add(`${toolName}\u0000${pending.ordinal}`);
|
|
126
|
+
pendingToolCalls.push({ toolName, ordinal: pending.ordinal, toolCallId });
|
|
127
|
+
if (toolCallId !== undefined)
|
|
128
|
+
pendingByToolCallId.set(toolCallId, { toolName, ordinal: pending.ordinal });
|
|
129
|
+
return pending.ordinal;
|
|
130
|
+
}
|
|
131
|
+
const ordinal = (rows.at(-1)?.ordinal ?? -1) + 1;
|
|
132
|
+
ports.sql.transactionSync(() => {
|
|
133
|
+
ports.sql.exec("INSERT INTO __telnyx_agent_harness_tool_calls(run_id,tool_name,ordinal,provider_call_id,journal_marker) VALUES (?,?,?,NULL,?)", options.runId, toolName, ordinal, markerFor(toolName, ordinal));
|
|
134
|
+
});
|
|
135
|
+
claimedToolCallOrdinals.add(`${toolName}\u0000${ordinal}`);
|
|
136
|
+
pendingToolCalls.push({ toolName, ordinal, toolCallId });
|
|
137
|
+
if (toolCallId !== undefined)
|
|
138
|
+
pendingByToolCallId.set(toolCallId, { toolName, ordinal });
|
|
139
|
+
return ordinal;
|
|
140
|
+
};
|
|
141
|
+
const associateToolCallJournal = (calls) => {
|
|
142
|
+
if (calls.length !== pendingToolCalls.length)
|
|
143
|
+
throw new Error("durable tool call journal does not match executed calls");
|
|
144
|
+
ports.sql.transactionSync(() => {
|
|
145
|
+
for (const call of calls) {
|
|
146
|
+
// Match by toolCallId when the reservation recorded one, so the
|
|
147
|
+
// association is independent of concurrent execution-start order.
|
|
148
|
+
const pending = call.toolCallId !== undefined && pendingByToolCallId.has(call.toolCallId)
|
|
149
|
+
? pendingByToolCallId.get(call.toolCallId)
|
|
150
|
+
: pendingToolCalls.find((p) => p.toolName === call.toolName && p.toolCallId === undefined);
|
|
151
|
+
if (pending === undefined)
|
|
152
|
+
throw new Error("durable tool call journal does not match executed calls");
|
|
153
|
+
const existing = first(ports.sql.exec("SELECT provider_call_id FROM __telnyx_agent_harness_tool_calls WHERE run_id = ? AND tool_name = ? AND ordinal = ?", options.runId, pending.toolName, pending.ordinal).toArray());
|
|
154
|
+
if (existing === undefined || (existing.provider_call_id !== null && journaledToolCallMarkers.has(markerFor(pending.toolName, pending.ordinal))
|
|
155
|
+
&& existing.provider_call_id !== call.toolCallId)) {
|
|
156
|
+
throw new Error("durable tool call journal conflicts with committed history");
|
|
157
|
+
}
|
|
158
|
+
ports.sql.exec("UPDATE __telnyx_agent_harness_tool_calls SET provider_call_id = ? WHERE run_id = ? AND tool_name = ? AND ordinal = ?", call.toolCallId, options.runId, pending.toolName, pending.ordinal);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
// Build the marker map keyed by toolCallId, using the matched pending record.
|
|
162
|
+
const markers = new Map();
|
|
163
|
+
for (const call of calls) {
|
|
164
|
+
const pending = call.toolCallId !== undefined && pendingByToolCallId.has(call.toolCallId)
|
|
165
|
+
? pendingByToolCallId.get(call.toolCallId)
|
|
166
|
+
: pendingToolCalls.find((p) => p.toolName === call.toolName && p.toolCallId === undefined);
|
|
167
|
+
if (pending === undefined)
|
|
168
|
+
throw new Error("durable tool call journal does not match executed calls");
|
|
169
|
+
markers.set(call.toolCallId, markerFor(pending.toolName, pending.ordinal));
|
|
170
|
+
}
|
|
171
|
+
pendingToolCalls.length = 0;
|
|
172
|
+
pendingByToolCallId.clear();
|
|
173
|
+
return markers;
|
|
174
|
+
};
|
|
175
|
+
const executeStep = async (name, fn, stepOptions) => {
|
|
176
|
+
boundedNonblank(name, "step name", MAX_STEP_NAME_BYTES);
|
|
177
|
+
const version = stepOptions.version ?? options.version;
|
|
178
|
+
boundedNonblank(version, "step version", MAX_VERSION_BYTES);
|
|
179
|
+
const inputFingerprint = stepOptions.input === undefined
|
|
180
|
+
? defaultInputFingerprint
|
|
181
|
+
: fingerprint(canonicalJson(stepOptions.input));
|
|
182
|
+
ensureHarnessDurabilitySchema(ports);
|
|
183
|
+
const identity = fingerprint(`${options.runId}\u0000${name}`);
|
|
184
|
+
const existing = ports.sql.transactionSync(() => {
|
|
185
|
+
const run = readRun();
|
|
186
|
+
if (run === undefined)
|
|
187
|
+
throw new Error("step run does not exist");
|
|
188
|
+
if (run.status === "outcome_unknown")
|
|
189
|
+
throw new Error("step outcome unknown; reconcile before retrying");
|
|
190
|
+
if (run.status !== "accepted" && run.status !== "running") {
|
|
191
|
+
throw new Error("step run is no longer executable");
|
|
192
|
+
}
|
|
193
|
+
const row = readStep(name);
|
|
194
|
+
if (row === undefined)
|
|
195
|
+
return undefined;
|
|
196
|
+
if (row.input_fingerprint !== inputFingerprint)
|
|
197
|
+
throw new Error("step input fingerprint does not match committed record");
|
|
198
|
+
if (row.version !== version)
|
|
199
|
+
throw new Error("step version does not match committed record");
|
|
200
|
+
if (row.status === "effect_started") {
|
|
201
|
+
return row;
|
|
202
|
+
}
|
|
203
|
+
if (row.status === "prepared") {
|
|
204
|
+
ports.sql.exec("DELETE FROM __telnyx_agent_harness_steps WHERE run_id = ? AND name = ? AND status = 'prepared'", options.runId, name);
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
return row;
|
|
208
|
+
});
|
|
209
|
+
if (existing?.status === "effect_started") {
|
|
210
|
+
ports.sql.transactionSync(() => {
|
|
211
|
+
ports.sql.exec("UPDATE __telnyx_agent_harness_runs SET status = 'outcome_unknown', failure_code = 'HARNESS_OUTCOME_UNKNOWN', updated_at = ? WHERE id = ? AND status IN ('accepted','running')", ports.clock.now(), options.runId);
|
|
212
|
+
});
|
|
213
|
+
throw new Error("step outcome unknown; reconcile before retrying");
|
|
214
|
+
}
|
|
215
|
+
if (existing !== undefined) {
|
|
216
|
+
if (existing.output_failure === TOOL_OUTPUT_SERIALIZATION_FAILURE)
|
|
217
|
+
throw new HarnessStepOutputSerializationError();
|
|
218
|
+
return JSON.parse(existing.output);
|
|
219
|
+
}
|
|
220
|
+
ports.sql.transactionSync(() => {
|
|
221
|
+
const next = first(ports.sql.exec("SELECT COALESCE(MAX(journal_seq), 0) + 1 AS next FROM __telnyx_agent_harness_steps WHERE run_id = ?", options.runId).toArray()).next;
|
|
222
|
+
ports.sql.exec("INSERT INTO __telnyx_agent_harness_steps(run_id,name,identity,input_fingerprint,version,status,output,journal_seq) VALUES (?,?,?,?,?,'prepared',NULL,?)", options.runId, name, identity, inputFingerprint, version, next);
|
|
223
|
+
});
|
|
224
|
+
checkpoint(options.checkpoints, "before_effect");
|
|
225
|
+
if (stepOptions.external === true) {
|
|
226
|
+
ports.sql.transactionSync(() => {
|
|
227
|
+
// This conditional transition is the cancellation fence: when it
|
|
228
|
+
// wins, cancel observes effect_started and preserves uncertainty;
|
|
229
|
+
// when cancellation wins first, no external effect may begin. An
|
|
230
|
+
// accepted row is also eligible for direct durable-context callers.
|
|
231
|
+
stepOptions.beforeEffect?.();
|
|
232
|
+
ports.sql.exec("UPDATE __telnyx_agent_harness_steps SET status = 'effect_started' WHERE run_id = ? AND name = ? AND status = 'prepared' AND EXISTS (SELECT 1 FROM __telnyx_agent_harness_runs WHERE id = ? AND status IN ('accepted','running'))", options.runId, name, options.runId);
|
|
233
|
+
if (readStep(name)?.status !== "effect_started") {
|
|
234
|
+
throw new Error("external step cannot start after run is no longer executable");
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
const output = await fn();
|
|
239
|
+
checkpoint(options.checkpoints, "after_effect_before_memo");
|
|
240
|
+
const serialized = serializeStepOutput(output, stepOptions.external === true);
|
|
241
|
+
ports.sql.transactionSync(() => {
|
|
242
|
+
ports.sql.exec("UPDATE __telnyx_agent_harness_steps SET status = 'completed', output = ?, output_failure = ? WHERE run_id = ? AND name = ?", serialized.output, serialized.failure, options.runId, name);
|
|
243
|
+
});
|
|
244
|
+
checkpoint(options.checkpoints, "after_memo");
|
|
245
|
+
if (serialized.failure !== null)
|
|
246
|
+
throw new HarnessStepOutputSerializationError();
|
|
247
|
+
return (stepOptions.external === true && output === undefined ? null : output);
|
|
248
|
+
};
|
|
249
|
+
return Object.freeze({
|
|
250
|
+
runId: options.runId,
|
|
251
|
+
isContinuation: options.isContinuation === true,
|
|
252
|
+
isCompleted(name, stepOptions = {}) {
|
|
253
|
+
boundedNonblank(name, "step name", MAX_STEP_NAME_BYTES);
|
|
254
|
+
const version = stepOptions.version ?? options.version;
|
|
255
|
+
boundedNonblank(version, "step version", MAX_VERSION_BYTES);
|
|
256
|
+
const inputFingerprint = stepOptions.input === undefined
|
|
257
|
+
? defaultInputFingerprint
|
|
258
|
+
: fingerprint(canonicalJson(stepOptions.input));
|
|
259
|
+
ensureHarnessDurabilitySchema(ports);
|
|
260
|
+
const row = readStep(name);
|
|
261
|
+
if (row === undefined)
|
|
262
|
+
return false;
|
|
263
|
+
if (row.input_fingerprint !== inputFingerprint)
|
|
264
|
+
throw new Error("step input fingerprint does not match committed record");
|
|
265
|
+
if (row.version !== version)
|
|
266
|
+
throw new Error("step version does not match committed record");
|
|
267
|
+
return row.status === "completed";
|
|
268
|
+
},
|
|
269
|
+
nextToolCallOrdinal(toolName, toolCallId) {
|
|
270
|
+
return toolCallOrdinal(toolName, toolCallId);
|
|
271
|
+
},
|
|
272
|
+
associateToolCallJournal(calls) {
|
|
273
|
+
return associateToolCallJournal(calls);
|
|
274
|
+
},
|
|
275
|
+
reconcileToolCallJournal(journalMarkers) {
|
|
276
|
+
journaledToolCallMarkers = new Set(journalMarkers);
|
|
277
|
+
},
|
|
278
|
+
async step(name, fn, stepOptions = {}) {
|
|
279
|
+
boundedNonblank(name, "step name", MAX_STEP_NAME_BYTES);
|
|
280
|
+
const version = stepOptions.version ?? options.version;
|
|
281
|
+
boundedNonblank(version, "step version", MAX_VERSION_BYTES);
|
|
282
|
+
const inputFingerprint = stepOptions.input === undefined
|
|
283
|
+
? defaultInputFingerprint
|
|
284
|
+
: fingerprint(canonicalJson(stepOptions.input));
|
|
285
|
+
const activeKey = `${options.runId}\u0000${name}`;
|
|
286
|
+
const inFlight = activeSteps.get(activeKey);
|
|
287
|
+
if (inFlight !== undefined) {
|
|
288
|
+
if (inFlight.inputFingerprint !== inputFingerprint)
|
|
289
|
+
throw new Error("step input fingerprint does not match committed record");
|
|
290
|
+
if (inFlight.version !== version)
|
|
291
|
+
throw new Error("step version does not match committed record");
|
|
292
|
+
return inFlight.execution;
|
|
293
|
+
}
|
|
294
|
+
// Promise scheduling defers executeStep until the live claim is visible,
|
|
295
|
+
// so a synchronous fn reentry cannot erase its prepared row or start a
|
|
296
|
+
// second effect.
|
|
297
|
+
const execution = Promise.resolve().then(() => executeStep(name, fn, stepOptions));
|
|
298
|
+
const active = { inputFingerprint, version, execution };
|
|
299
|
+
activeSteps.set(activeKey, active);
|
|
300
|
+
try {
|
|
301
|
+
return await execution;
|
|
302
|
+
}
|
|
303
|
+
finally {
|
|
304
|
+
if (activeSteps.get(activeKey) === active)
|
|
305
|
+
activeSteps.delete(activeKey);
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
//# sourceMappingURL=steps.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type HarnessToolContext = Readonly<{
|
|
2
|
+
runId: string;
|
|
3
|
+
toolName: string;
|
|
4
|
+
ordinal: number;
|
|
5
|
+
}>;
|
|
6
|
+
export declare function withHarnessToolContext<T>(value: HarnessToolContext, operation: () => Promise<T>): Promise<T>;
|
|
7
|
+
export declare function currentHarnessToolContext(): HarnessToolContext | undefined;
|
|
8
|
+
//# sourceMappingURL=tool-context.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
const context = new AsyncLocalStorage();
|
|
3
|
+
export function withHarnessToolContext(value, operation) {
|
|
4
|
+
return context.run(value, operation);
|
|
5
|
+
}
|
|
6
|
+
export function currentHarnessToolContext() {
|
|
7
|
+
return context.getStore();
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=tool-context.js.map
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { AgentHarnessPorts } from "./ports.js";
|
|
2
|
+
declare const TOOL_WORKSPACE_CAPABILITY: unique symbol;
|
|
3
|
+
export interface VirtualWorkspaceLimits {
|
|
4
|
+
readonly maxFileBytes?: number;
|
|
5
|
+
readonly maxWorkspaceBytes?: number;
|
|
6
|
+
readonly maxOutputBytes?: number;
|
|
7
|
+
readonly maxResults?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface WorkspaceWriteOptions {
|
|
10
|
+
readonly expectedRevision?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface WorkspaceEntry {
|
|
13
|
+
readonly path: string;
|
|
14
|
+
readonly bytes: number;
|
|
15
|
+
readonly revision: number;
|
|
16
|
+
}
|
|
17
|
+
export interface WorkspaceToolEntry extends WorkspaceEntry {
|
|
18
|
+
readonly origin: "workspace";
|
|
19
|
+
}
|
|
20
|
+
export interface WorkspaceRead extends WorkspaceEntry {
|
|
21
|
+
readonly content: string;
|
|
22
|
+
readonly truncated?: true;
|
|
23
|
+
}
|
|
24
|
+
export interface WorkspaceJournalEntry {
|
|
25
|
+
readonly sequence: number;
|
|
26
|
+
readonly operation: "write" | "edit";
|
|
27
|
+
readonly path: string;
|
|
28
|
+
readonly revision: number;
|
|
29
|
+
readonly bytes: number;
|
|
30
|
+
}
|
|
31
|
+
export interface WorkspaceSkill {
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly path: string;
|
|
34
|
+
readonly origin: "workspace";
|
|
35
|
+
}
|
|
36
|
+
export interface WorkspaceSkillCatalog {
|
|
37
|
+
readonly skills: readonly WorkspaceSkill[];
|
|
38
|
+
readonly truncated?: true;
|
|
39
|
+
}
|
|
40
|
+
export interface ActivatedWorkspaceSkill {
|
|
41
|
+
readonly name: string;
|
|
42
|
+
readonly path: string;
|
|
43
|
+
readonly origin: "activated-skill";
|
|
44
|
+
readonly content: string;
|
|
45
|
+
}
|
|
46
|
+
export type PromptSource = Readonly<{
|
|
47
|
+
origin: "application";
|
|
48
|
+
}> | Readonly<{
|
|
49
|
+
origin: "activated-skill";
|
|
50
|
+
name: string;
|
|
51
|
+
path: string;
|
|
52
|
+
}>;
|
|
53
|
+
export interface VirtualWorkspace {
|
|
54
|
+
read(path: string): Promise<WorkspaceRead | undefined>;
|
|
55
|
+
write(path: string, content: string, options?: WorkspaceWriteOptions): Promise<WorkspaceEntry>;
|
|
56
|
+
edit(path: string, oldText: string, newText: string, options?: WorkspaceWriteOptions): Promise<WorkspaceEntry>;
|
|
57
|
+
list(): Promise<Readonly<{
|
|
58
|
+
entries: readonly WorkspaceEntry[];
|
|
59
|
+
truncated?: true;
|
|
60
|
+
}>>;
|
|
61
|
+
find(needle: string): Promise<Readonly<{
|
|
62
|
+
matches: readonly WorkspaceEntry[];
|
|
63
|
+
truncated?: true;
|
|
64
|
+
}>>;
|
|
65
|
+
grep(needle: string): Promise<Readonly<{
|
|
66
|
+
matches: readonly {
|
|
67
|
+
path: string;
|
|
68
|
+
line: number;
|
|
69
|
+
text: string;
|
|
70
|
+
}[];
|
|
71
|
+
truncated?: true;
|
|
72
|
+
}>>;
|
|
73
|
+
journal(options?: Readonly<{
|
|
74
|
+
limit?: number;
|
|
75
|
+
}>): Promise<readonly WorkspaceJournalEntry[]>;
|
|
76
|
+
discoverSkills(): Promise<WorkspaceSkillCatalog>;
|
|
77
|
+
activateSkill(name: string): Promise<ActivatedWorkspaceSkill>;
|
|
78
|
+
assemblePrompt(base: string, skills: readonly string[]): Promise<Readonly<{
|
|
79
|
+
text: string;
|
|
80
|
+
sources: readonly PromptSource[];
|
|
81
|
+
}>>;
|
|
82
|
+
}
|
|
83
|
+
/** A concrete workspace that can atomically preflight tool mutation results. */
|
|
84
|
+
export interface ToolVirtualWorkspace extends VirtualWorkspace {
|
|
85
|
+
readonly [TOOL_WORKSPACE_CAPABILITY]: true;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Persistent, actor-local text workspace over the existing private harness SQL
|
|
89
|
+
* port. It intentionally exposes no filesystem, process, module-loading, or
|
|
90
|
+
* dynamic-extension capability.
|
|
91
|
+
*/
|
|
92
|
+
export declare function createVirtualWorkspace(ports: AgentHarnessPorts, limits?: VirtualWorkspaceLimits): ToolVirtualWorkspace;
|
|
93
|
+
/**
|
|
94
|
+
* Explicit server-side tools backed only by a {@link VirtualWorkspace}. Skill
|
|
95
|
+
* documents remain returned data; this factory never evaluates their content.
|
|
96
|
+
*/
|
|
97
|
+
export declare function createVirtualWorkspaceTools(workspace: ToolVirtualWorkspace): Readonly<{
|
|
98
|
+
read_file: import("ai").Tool<{
|
|
99
|
+
path: string;
|
|
100
|
+
}, Readonly<{
|
|
101
|
+
content: string;
|
|
102
|
+
truncated?: true;
|
|
103
|
+
path: string;
|
|
104
|
+
bytes: number;
|
|
105
|
+
revision: number;
|
|
106
|
+
origin: "workspace";
|
|
107
|
+
} | {
|
|
108
|
+
path: string;
|
|
109
|
+
found: false;
|
|
110
|
+
origin: "workspace";
|
|
111
|
+
}>>;
|
|
112
|
+
write_file: import("ai").Tool<{
|
|
113
|
+
path: string;
|
|
114
|
+
content: string;
|
|
115
|
+
expectedRevision?: number | undefined;
|
|
116
|
+
}, WorkspaceToolEntry>;
|
|
117
|
+
edit_file: import("ai").Tool<{
|
|
118
|
+
path: string;
|
|
119
|
+
oldText: string;
|
|
120
|
+
newText: string;
|
|
121
|
+
expectedRevision?: number | undefined;
|
|
122
|
+
}, WorkspaceToolEntry>;
|
|
123
|
+
list_files: import("ai").Tool<Record<string, never>, Readonly<{
|
|
124
|
+
entries: readonly WorkspaceEntry[];
|
|
125
|
+
truncated?: true;
|
|
126
|
+
origin: "workspace";
|
|
127
|
+
}>>;
|
|
128
|
+
find_files: import("ai").Tool<{
|
|
129
|
+
needle: string;
|
|
130
|
+
}, Readonly<{
|
|
131
|
+
matches: readonly WorkspaceEntry[];
|
|
132
|
+
truncated?: true;
|
|
133
|
+
origin: "workspace";
|
|
134
|
+
}>>;
|
|
135
|
+
grep_files: import("ai").Tool<{
|
|
136
|
+
needle: string;
|
|
137
|
+
}, Readonly<{
|
|
138
|
+
matches: readonly {
|
|
139
|
+
path: string;
|
|
140
|
+
line: number;
|
|
141
|
+
text: string;
|
|
142
|
+
}[];
|
|
143
|
+
truncated?: true;
|
|
144
|
+
origin: "workspace";
|
|
145
|
+
}>>;
|
|
146
|
+
activate_skill: import("ai").Tool<{
|
|
147
|
+
name: string;
|
|
148
|
+
}, ActivatedWorkspaceSkill>;
|
|
149
|
+
}>;
|
|
150
|
+
export {};
|
|
151
|
+
//# sourceMappingURL=workspace.d.ts.map
|