@modelrelay/sdk 5.1.0 → 8.0.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 +132 -55
- package/dist/api-B7SmXjnr.d.cts +5917 -0
- package/dist/api-B7SmXjnr.d.ts +5917 -0
- package/dist/api-CdHqjsU_.d.cts +6062 -0
- package/dist/api-CdHqjsU_.d.ts +6062 -0
- package/dist/api-DP9MoKHy.d.cts +5993 -0
- package/dist/api-DP9MoKHy.d.ts +5993 -0
- package/dist/billing/index.d.cts +1 -1
- package/dist/billing/index.d.ts +1 -1
- package/dist/chunk-27KGKJLT.js +1194 -0
- package/dist/chunk-HHBAD7FF.js +1133 -0
- package/dist/chunk-PKGWFDGU.js +1185 -0
- package/dist/chunk-SJC7Q4NK.js +1199 -0
- package/dist/chunk-YQWOQ74P.js +1166 -0
- package/dist/index.cjs +3180 -891
- package/dist/index.d.cts +950 -85
- package/dist/index.d.ts +950 -85
- package/dist/index.js +3099 -899
- package/dist/node.cjs +6 -2
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +1 -1
- package/dist/tools-Bxdv0Np2.d.cts +1052 -0
- package/dist/tools-Bxdv0Np2.d.ts +1052 -0
- package/dist/tools-DHCGz_lx.d.cts +1041 -0
- package/dist/tools-DHCGz_lx.d.ts +1041 -0
- package/dist/tools-ZpcYacSo.d.cts +1000 -0
- package/dist/tools-ZpcYacSo.d.ts +1000 -0
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
APIError,
|
|
3
|
+
AgentMaxTurnsError,
|
|
3
4
|
BillingProviders,
|
|
4
5
|
ConfigError,
|
|
5
6
|
ContentPartTypes,
|
|
@@ -26,7 +27,6 @@ import {
|
|
|
26
27
|
ToolRegistry,
|
|
27
28
|
ToolTypes,
|
|
28
29
|
TransportError,
|
|
29
|
-
WebToolIntents,
|
|
30
30
|
WorkflowValidationError,
|
|
31
31
|
asModelId,
|
|
32
32
|
asProviderId,
|
|
@@ -39,13 +39,20 @@ import {
|
|
|
39
39
|
createRetryMessages,
|
|
40
40
|
createSystemMessage,
|
|
41
41
|
createToolCall,
|
|
42
|
+
createTypedTool,
|
|
42
43
|
createUsage,
|
|
43
44
|
createUserMessage,
|
|
44
|
-
createWebTool,
|
|
45
45
|
executeWithRetry,
|
|
46
46
|
firstToolCall,
|
|
47
47
|
formatToolErrorForModel,
|
|
48
|
+
getAllToolCalls,
|
|
49
|
+
getAssistantText,
|
|
48
50
|
getRetryableErrors,
|
|
51
|
+
getToolArgs,
|
|
52
|
+
getToolArgsRaw,
|
|
53
|
+
getToolName,
|
|
54
|
+
getTypedToolCall,
|
|
55
|
+
getTypedToolCalls,
|
|
49
56
|
hasRetryableErrors,
|
|
50
57
|
hasToolCalls,
|
|
51
58
|
mergeMetrics,
|
|
@@ -54,17 +61,15 @@ import {
|
|
|
54
61
|
normalizeModelId,
|
|
55
62
|
normalizeStopReason,
|
|
56
63
|
parseErrorResponse,
|
|
57
|
-
|
|
58
|
-
parseToolArgsRaw,
|
|
64
|
+
parseTypedToolCall,
|
|
59
65
|
respondToToolCall,
|
|
60
66
|
stopReasonToString,
|
|
61
67
|
toolChoiceAuto,
|
|
62
68
|
toolChoiceNone,
|
|
63
69
|
toolChoiceRequired,
|
|
64
70
|
toolResultMessage,
|
|
65
|
-
tryParseToolArgs,
|
|
66
71
|
zodToJsonSchema
|
|
67
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-SJC7Q4NK.js";
|
|
68
73
|
import {
|
|
69
74
|
__export
|
|
70
75
|
} from "./chunk-MLKGABMK.js";
|
|
@@ -154,6 +159,9 @@ var AuthClient = class {
|
|
|
154
159
|
if (typeof request.ttlSeconds === "number") {
|
|
155
160
|
payload.ttl_seconds = request.ttlSeconds;
|
|
156
161
|
}
|
|
162
|
+
if (request.tierCode) {
|
|
163
|
+
payload.tier_code = request.tierCode;
|
|
164
|
+
}
|
|
157
165
|
const apiResp = await this.http.json("/auth/customer-token", {
|
|
158
166
|
method: "POST",
|
|
159
167
|
body: payload,
|
|
@@ -209,7 +217,8 @@ var AuthClient = class {
|
|
|
209
217
|
});
|
|
210
218
|
return this.customerToken({
|
|
211
219
|
customerExternalId: externalId,
|
|
212
|
-
ttlSeconds: request.ttlSeconds
|
|
220
|
+
ttlSeconds: request.ttlSeconds,
|
|
221
|
+
tierCode: request.tierCode
|
|
213
222
|
});
|
|
214
223
|
}
|
|
215
224
|
/**
|
|
@@ -327,13 +336,38 @@ var ResponseBuilder = class _ResponseBuilder {
|
|
|
327
336
|
patch.options ? { ...this.options, ...patch.options } : this.options
|
|
328
337
|
);
|
|
329
338
|
}
|
|
330
|
-
/**
|
|
339
|
+
/**
|
|
340
|
+
* Set the provider for this request.
|
|
341
|
+
*
|
|
342
|
+
* Accepts either a string or ProviderId for convenience.
|
|
343
|
+
*
|
|
344
|
+
* @example
|
|
345
|
+
* ```typescript
|
|
346
|
+
* .provider("anthropic") // String works
|
|
347
|
+
* .provider(asProviderId("anthropic")) // ProviderId also works
|
|
348
|
+
* ```
|
|
349
|
+
*/
|
|
331
350
|
provider(provider) {
|
|
332
|
-
return this.with({ body: { provider } });
|
|
351
|
+
return this.with({ body: { provider: asProviderId(provider) } });
|
|
333
352
|
}
|
|
334
|
-
/**
|
|
353
|
+
/**
|
|
354
|
+
* Set the model for this request.
|
|
355
|
+
*
|
|
356
|
+
* Accepts either a string or ModelId for convenience.
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* ```typescript
|
|
360
|
+
* .model("claude-sonnet-4-5") // String works
|
|
361
|
+
* .model(asModelId("claude-sonnet-4-5")) // ModelId also works
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
335
364
|
model(model) {
|
|
336
|
-
return this.with({ body: { model } });
|
|
365
|
+
return this.with({ body: { model: asModelId(model) } });
|
|
366
|
+
}
|
|
367
|
+
/** @returns A new builder with state-scoped tool state. */
|
|
368
|
+
stateId(stateId) {
|
|
369
|
+
const state_id = stateId.trim();
|
|
370
|
+
return this.with({ body: { state_id: state_id || void 0 } });
|
|
337
371
|
}
|
|
338
372
|
/** @returns A new builder with the full input array replaced. */
|
|
339
373
|
input(items) {
|
|
@@ -481,6 +515,111 @@ var ResponseBuilder = class _ResponseBuilder {
|
|
|
481
515
|
signal(signal) {
|
|
482
516
|
return this.with({ options: { signal } });
|
|
483
517
|
}
|
|
518
|
+
// =========================================================================
|
|
519
|
+
// Conversation Continuation Helpers
|
|
520
|
+
// =========================================================================
|
|
521
|
+
/**
|
|
522
|
+
* Add an assistant message with tool calls from a previous response.
|
|
523
|
+
*
|
|
524
|
+
* This is useful for continuing a conversation after handling tool calls.
|
|
525
|
+
*
|
|
526
|
+
* @example
|
|
527
|
+
* ```typescript
|
|
528
|
+
* const response = await mr.responses.create(request);
|
|
529
|
+
* if (hasToolCalls(response)) {
|
|
530
|
+
* const toolCalls = response.output[0].toolCalls!;
|
|
531
|
+
* const results = await registry.executeAll(toolCalls);
|
|
532
|
+
*
|
|
533
|
+
* const followUp = await mr.responses.create(
|
|
534
|
+
* mr.responses.new()
|
|
535
|
+
* .model("claude-sonnet-4-5")
|
|
536
|
+
* .user("What's the weather in Paris?")
|
|
537
|
+
* .assistantToolCalls(toolCalls)
|
|
538
|
+
* .toolResults(results.map(r => ({ id: r.toolCallId, result: r.result })))
|
|
539
|
+
* .build()
|
|
540
|
+
* );
|
|
541
|
+
* }
|
|
542
|
+
* ```
|
|
543
|
+
*/
|
|
544
|
+
assistantToolCalls(toolCalls, content) {
|
|
545
|
+
return this.item(assistantMessageWithToolCalls(content ?? "", toolCalls));
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Add tool results to the conversation.
|
|
549
|
+
*
|
|
550
|
+
* @example
|
|
551
|
+
* ```typescript
|
|
552
|
+
* .toolResults([
|
|
553
|
+
* { id: "call_123", result: { temp: 72 } },
|
|
554
|
+
* { id: "call_456", result: "File contents here" },
|
|
555
|
+
* ])
|
|
556
|
+
* ```
|
|
557
|
+
*/
|
|
558
|
+
toolResults(results) {
|
|
559
|
+
let builder = this;
|
|
560
|
+
for (const r of results) {
|
|
561
|
+
const content = typeof r.result === "string" ? r.result : JSON.stringify(r.result);
|
|
562
|
+
builder = builder.item(toolResultMessage(r.id, content));
|
|
563
|
+
}
|
|
564
|
+
return builder;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Add a single tool result to the conversation.
|
|
568
|
+
*
|
|
569
|
+
* @example
|
|
570
|
+
* ```typescript
|
|
571
|
+
* .toolResult("call_123", { temp: 72, unit: "fahrenheit" })
|
|
572
|
+
* ```
|
|
573
|
+
*/
|
|
574
|
+
toolResult(toolCallId, result) {
|
|
575
|
+
const content = typeof result === "string" ? result : JSON.stringify(result);
|
|
576
|
+
return this.item(toolResultMessage(toolCallId, content));
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Continue from a previous response that contains tool calls.
|
|
580
|
+
*
|
|
581
|
+
* This is the most ergonomic way to continue a conversation after handling tools.
|
|
582
|
+
* It automatically adds the assistant's tool call message and your tool results.
|
|
583
|
+
*
|
|
584
|
+
* @example
|
|
585
|
+
* ```typescript
|
|
586
|
+
* const response = await mr.responses.create(request);
|
|
587
|
+
*
|
|
588
|
+
* if (hasToolCalls(response)) {
|
|
589
|
+
* const toolCalls = response.output[0].toolCalls!;
|
|
590
|
+
* const results = await registry.executeAll(toolCalls);
|
|
591
|
+
*
|
|
592
|
+
* const followUp = await mr.responses.create(
|
|
593
|
+
* mr.responses.new()
|
|
594
|
+
* .model("claude-sonnet-4-5")
|
|
595
|
+
* .tools(myTools)
|
|
596
|
+
* .user("What's the weather in Paris?")
|
|
597
|
+
* .continueFrom(response, results.map(r => ({
|
|
598
|
+
* id: r.toolCallId,
|
|
599
|
+
* result: r.result,
|
|
600
|
+
* })))
|
|
601
|
+
* .build()
|
|
602
|
+
* );
|
|
603
|
+
* }
|
|
604
|
+
* ```
|
|
605
|
+
*/
|
|
606
|
+
continueFrom(response, toolResults) {
|
|
607
|
+
const toolCalls = [];
|
|
608
|
+
for (const item of response.output || []) {
|
|
609
|
+
if (item.toolCalls) {
|
|
610
|
+
toolCalls.push(...item.toolCalls);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
if (toolCalls.length === 0) {
|
|
614
|
+
throw new ConfigError(
|
|
615
|
+
"continueFrom requires a response with tool calls"
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
const assistantText = getAssistantText(response);
|
|
619
|
+
return this.assistantToolCalls(toolCalls, assistantText).toolResults(
|
|
620
|
+
toolResults
|
|
621
|
+
);
|
|
622
|
+
}
|
|
484
623
|
/** @returns A finalized, immutable request payload. */
|
|
485
624
|
build() {
|
|
486
625
|
const input = (this.body.input ?? []).slice();
|
|
@@ -490,6 +629,7 @@ var ResponseBuilder = class _ResponseBuilder {
|
|
|
490
629
|
const body = {
|
|
491
630
|
provider: this.body.provider,
|
|
492
631
|
model: this.body.model,
|
|
632
|
+
state_id: this.body.state_id,
|
|
493
633
|
input,
|
|
494
634
|
output_format: this.body.output_format,
|
|
495
635
|
max_output_tokens: this.body.max_output_tokens,
|
|
@@ -1806,7 +1946,7 @@ function parseOutputName(raw) {
|
|
|
1806
1946
|
var WorkflowKinds = {
|
|
1807
1947
|
WorkflowIntent: "workflow"
|
|
1808
1948
|
};
|
|
1809
|
-
var
|
|
1949
|
+
var WorkflowNodeTypesIntent = {
|
|
1810
1950
|
LLM: "llm",
|
|
1811
1951
|
JoinAll: "join.all",
|
|
1812
1952
|
JoinAny: "join.any",
|
|
@@ -1876,6 +2016,25 @@ var nodeWaitingSchema = z.object({
|
|
|
1876
2016
|
pending_tool_calls: z.array(pendingToolCallSchema).min(1),
|
|
1877
2017
|
reason: z.string().min(1)
|
|
1878
2018
|
}).strict();
|
|
2019
|
+
var userAskOptionSchema = z.object({
|
|
2020
|
+
label: z.string().min(1),
|
|
2021
|
+
description: z.string().optional()
|
|
2022
|
+
}).strict();
|
|
2023
|
+
var nodeUserAskSchema = z.object({
|
|
2024
|
+
step: z.number().int().nonnegative(),
|
|
2025
|
+
request_id: z.string().min(1),
|
|
2026
|
+
tool_call: toolCallWithArgumentsSchema,
|
|
2027
|
+
question: z.string().min(1),
|
|
2028
|
+
options: z.array(userAskOptionSchema).optional(),
|
|
2029
|
+
allow_freeform: z.boolean()
|
|
2030
|
+
}).strict();
|
|
2031
|
+
var nodeUserAnswerSchema = z.object({
|
|
2032
|
+
step: z.number().int().nonnegative(),
|
|
2033
|
+
request_id: z.string().min(1),
|
|
2034
|
+
tool_call: toolCallSchema,
|
|
2035
|
+
answer: z.string(),
|
|
2036
|
+
is_freeform: z.boolean()
|
|
2037
|
+
}).strict();
|
|
1879
2038
|
var baseSchema = {
|
|
1880
2039
|
envelope_version: z.literal("v2").optional().default("v2"),
|
|
1881
2040
|
run_id: z.string().min(1),
|
|
@@ -1927,6 +2086,18 @@ var runEventWireSchema = z.discriminatedUnion("type", [
|
|
|
1927
2086
|
node_id: z.string().min(1),
|
|
1928
2087
|
waiting: nodeWaitingSchema
|
|
1929
2088
|
}).strict(),
|
|
2089
|
+
z.object({
|
|
2090
|
+
...baseSchema,
|
|
2091
|
+
type: z.literal("node_user_ask"),
|
|
2092
|
+
node_id: z.string().min(1),
|
|
2093
|
+
user_ask: nodeUserAskSchema
|
|
2094
|
+
}).strict(),
|
|
2095
|
+
z.object({
|
|
2096
|
+
...baseSchema,
|
|
2097
|
+
type: z.literal("node_user_answer"),
|
|
2098
|
+
node_id: z.string().min(1),
|
|
2099
|
+
user_answer: nodeUserAnswerSchema
|
|
2100
|
+
}).strict(),
|
|
1930
2101
|
z.object({ ...baseSchema, type: z.literal("node_started"), node_id: z.string().min(1) }).strict(),
|
|
1931
2102
|
z.object({ ...baseSchema, type: z.literal("node_succeeded"), node_id: z.string().min(1) }).strict(),
|
|
1932
2103
|
z.object({
|
|
@@ -2020,6 +2191,10 @@ function parseRunEventV0(line) {
|
|
|
2020
2191
|
return { ...base, type: "node_tool_result", node_id: parseNodeId(res.data.node_id), tool_result: res.data.tool_result };
|
|
2021
2192
|
case "node_waiting":
|
|
2022
2193
|
return { ...base, type: "node_waiting", node_id: parseNodeId(res.data.node_id), waiting: res.data.waiting };
|
|
2194
|
+
case "node_user_ask":
|
|
2195
|
+
return { ...base, type: "node_user_ask", node_id: parseNodeId(res.data.node_id), user_ask: res.data.user_ask };
|
|
2196
|
+
case "node_user_answer":
|
|
2197
|
+
return { ...base, type: "node_user_answer", node_id: parseNodeId(res.data.node_id), user_answer: res.data.user_answer };
|
|
2023
2198
|
case "node_succeeded":
|
|
2024
2199
|
return { ...base, type: "node_succeeded", node_id: parseNodeId(res.data.node_id) };
|
|
2025
2200
|
case "node_failed":
|
|
@@ -2181,6 +2356,90 @@ var RunsClient = class {
|
|
|
2181
2356
|
if (options.input) {
|
|
2182
2357
|
payload.input = options.input;
|
|
2183
2358
|
}
|
|
2359
|
+
if (options.modelOverride?.trim()) {
|
|
2360
|
+
payload.model_override = options.modelOverride.trim();
|
|
2361
|
+
}
|
|
2362
|
+
if (options.modelOverrides) {
|
|
2363
|
+
const nodes = options.modelOverrides.nodes;
|
|
2364
|
+
const fanoutSubnodes = options.modelOverrides.fanoutSubnodes;
|
|
2365
|
+
if (nodes && Object.keys(nodes).length > 0 || fanoutSubnodes && fanoutSubnodes.length > 0) {
|
|
2366
|
+
payload.model_overrides = {
|
|
2367
|
+
nodes,
|
|
2368
|
+
fanout_subnodes: fanoutSubnodes?.map((entry) => ({
|
|
2369
|
+
parent_id: entry.parentId,
|
|
2370
|
+
subnode_id: entry.subnodeId,
|
|
2371
|
+
model: entry.model
|
|
2372
|
+
}))
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
if (options.stream !== void 0) {
|
|
2377
|
+
payload.stream = options.stream;
|
|
2378
|
+
}
|
|
2379
|
+
const out = await this.http.json(RUNS_PATH, {
|
|
2380
|
+
method: "POST",
|
|
2381
|
+
headers,
|
|
2382
|
+
body: payload,
|
|
2383
|
+
signal: options.signal,
|
|
2384
|
+
apiKey: authHeaders.apiKey,
|
|
2385
|
+
accessToken: authHeaders.accessToken,
|
|
2386
|
+
timeoutMs: options.timeoutMs,
|
|
2387
|
+
connectTimeoutMs: options.connectTimeoutMs,
|
|
2388
|
+
retry: options.retry,
|
|
2389
|
+
metrics,
|
|
2390
|
+
trace,
|
|
2391
|
+
context: { method: "POST", path: RUNS_PATH }
|
|
2392
|
+
});
|
|
2393
|
+
return { ...out, run_id: parseRunId(out.run_id), plan_hash: parsePlanHash(out.plan_hash) };
|
|
2394
|
+
}
|
|
2395
|
+
/**
|
|
2396
|
+
* Starts a workflow run using a precompiled plan hash.
|
|
2397
|
+
*
|
|
2398
|
+
* Use workflows.compile() to compile a workflow spec and obtain a plan_hash,
|
|
2399
|
+
* then use this method to start runs without re-compiling each time.
|
|
2400
|
+
* This is useful for workflows that are run repeatedly with the same structure
|
|
2401
|
+
* but different inputs.
|
|
2402
|
+
*
|
|
2403
|
+
* The plan_hash must have been compiled in the current server session;
|
|
2404
|
+
* if the server has restarted since compilation, the plan will not be found
|
|
2405
|
+
* and you'll need to recompile.
|
|
2406
|
+
*/
|
|
2407
|
+
async createFromPlan(planHash, options = {}) {
|
|
2408
|
+
const metrics = mergeMetrics(this.metrics, options.metrics);
|
|
2409
|
+
const trace = mergeTrace(this.trace, options.trace);
|
|
2410
|
+
const authHeaders = await this.auth.authForResponses();
|
|
2411
|
+
const headers = { ...options.headers || {} };
|
|
2412
|
+
this.applyCustomerHeader(headers, options.customerId);
|
|
2413
|
+
const payload = { plan_hash: planHash };
|
|
2414
|
+
if (options.sessionId?.trim()) {
|
|
2415
|
+
payload.session_id = options.sessionId.trim();
|
|
2416
|
+
}
|
|
2417
|
+
if (options.idempotencyKey?.trim()) {
|
|
2418
|
+
payload.options = { idempotency_key: options.idempotencyKey.trim() };
|
|
2419
|
+
}
|
|
2420
|
+
if (options.input) {
|
|
2421
|
+
payload.input = options.input;
|
|
2422
|
+
}
|
|
2423
|
+
if (options.modelOverride?.trim()) {
|
|
2424
|
+
payload.model_override = options.modelOverride.trim();
|
|
2425
|
+
}
|
|
2426
|
+
if (options.modelOverrides) {
|
|
2427
|
+
const nodes = options.modelOverrides.nodes;
|
|
2428
|
+
const fanoutSubnodes = options.modelOverrides.fanoutSubnodes;
|
|
2429
|
+
if (nodes && Object.keys(nodes).length > 0 || fanoutSubnodes && fanoutSubnodes.length > 0) {
|
|
2430
|
+
payload.model_overrides = {
|
|
2431
|
+
nodes,
|
|
2432
|
+
fanout_subnodes: fanoutSubnodes?.map((entry) => ({
|
|
2433
|
+
parent_id: entry.parentId,
|
|
2434
|
+
subnode_id: entry.subnodeId,
|
|
2435
|
+
model: entry.model
|
|
2436
|
+
}))
|
|
2437
|
+
};
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
if (options.stream !== void 0) {
|
|
2441
|
+
payload.stream = options.stream;
|
|
2442
|
+
}
|
|
2184
2443
|
const out = await this.http.json(RUNS_PATH, {
|
|
2185
2444
|
method: "POST",
|
|
2186
2445
|
headers,
|
|
@@ -2523,290 +2782,460 @@ var ImagesClient = class {
|
|
|
2523
2782
|
}
|
|
2524
2783
|
};
|
|
2525
2784
|
|
|
2526
|
-
// src/
|
|
2527
|
-
var
|
|
2528
|
-
var
|
|
2529
|
-
var
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
}
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
if (
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2785
|
+
// src/state_handles.ts
|
|
2786
|
+
var MAX_STATE_HANDLE_TTL_SECONDS = 31536e3;
|
|
2787
|
+
var STATE_HANDLES_PATH = "/state-handles";
|
|
2788
|
+
var StateHandlesClient = class {
|
|
2789
|
+
constructor(http, auth) {
|
|
2790
|
+
this.http = http;
|
|
2791
|
+
this.auth = auth;
|
|
2792
|
+
}
|
|
2793
|
+
/** Make an authenticated request to the state handles API. */
|
|
2794
|
+
async request(method, path, body) {
|
|
2795
|
+
const auth = await this.auth.authForResponses();
|
|
2796
|
+
return this.http.json(path, {
|
|
2797
|
+
method,
|
|
2798
|
+
body,
|
|
2799
|
+
apiKey: auth.apiKey,
|
|
2800
|
+
accessToken: auth.accessToken
|
|
2801
|
+
});
|
|
2802
|
+
}
|
|
2803
|
+
async create(request = {}) {
|
|
2804
|
+
if (request.ttl_seconds !== void 0) {
|
|
2805
|
+
if (request.ttl_seconds <= 0) {
|
|
2806
|
+
throw new Error("ttl_seconds must be positive");
|
|
2807
|
+
}
|
|
2808
|
+
if (request.ttl_seconds > MAX_STATE_HANDLE_TTL_SECONDS) {
|
|
2809
|
+
throw new Error("ttl_seconds exceeds maximum (1 year)");
|
|
2810
|
+
}
|
|
2549
2811
|
}
|
|
2550
|
-
return
|
|
2551
|
-
};
|
|
2552
|
-
}
|
|
2553
|
-
async function buildSessionInputWithContext(messages, options, defaultModel, resolveModelContext) {
|
|
2554
|
-
const strategy = options.contextManagement ?? "none";
|
|
2555
|
-
if (strategy === "none") {
|
|
2556
|
-
return messagesToInput(messages);
|
|
2812
|
+
return this.request("POST", STATE_HANDLES_PATH, request);
|
|
2557
2813
|
}
|
|
2558
|
-
|
|
2559
|
-
|
|
2814
|
+
async list(params = {}) {
|
|
2815
|
+
const { limit, offset } = params;
|
|
2816
|
+
if (limit !== void 0 && (limit <= 0 || limit > 100)) {
|
|
2817
|
+
throw new Error("limit must be between 1 and 100");
|
|
2818
|
+
}
|
|
2819
|
+
if (offset !== void 0 && offset < 0) {
|
|
2820
|
+
throw new Error("offset must be non-negative");
|
|
2821
|
+
}
|
|
2822
|
+
const query = new URLSearchParams();
|
|
2823
|
+
if (limit !== void 0) {
|
|
2824
|
+
query.set("limit", String(limit));
|
|
2825
|
+
}
|
|
2826
|
+
if (offset !== void 0 && offset > 0) {
|
|
2827
|
+
query.set("offset", String(offset));
|
|
2828
|
+
}
|
|
2829
|
+
const path = query.toString() ? `${STATE_HANDLES_PATH}?${query.toString()}` : STATE_HANDLES_PATH;
|
|
2830
|
+
return this.request("GET", path);
|
|
2560
2831
|
}
|
|
2561
|
-
|
|
2562
|
-
|
|
2832
|
+
async delete(stateId) {
|
|
2833
|
+
if (!stateId?.trim()) {
|
|
2834
|
+
throw new Error("state_id is required");
|
|
2835
|
+
}
|
|
2836
|
+
await this.request("DELETE", `${STATE_HANDLES_PATH}/${stateId}`);
|
|
2563
2837
|
}
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2838
|
+
};
|
|
2839
|
+
|
|
2840
|
+
// src/tool_loop.ts
|
|
2841
|
+
var DEFAULT_MAX_TURNS = 100;
|
|
2842
|
+
async function runToolLoop(config) {
|
|
2843
|
+
const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
2844
|
+
if (!Number.isFinite(maxTurns) || maxTurns <= 0) {
|
|
2845
|
+
throw new ConfigError("maxTurns must be a positive number");
|
|
2846
|
+
}
|
|
2847
|
+
const tools = config.tools ?? [];
|
|
2848
|
+
const history = config.input.slice();
|
|
2849
|
+
const usage = {
|
|
2850
|
+
inputTokens: 0,
|
|
2851
|
+
outputTokens: 0,
|
|
2852
|
+
totalTokens: 0,
|
|
2853
|
+
llmCalls: 0,
|
|
2854
|
+
toolCalls: 0
|
|
2855
|
+
};
|
|
2856
|
+
for (let turn = 0; turn < maxTurns; turn += 1) {
|
|
2857
|
+
let builder = config.client.new().input(history);
|
|
2858
|
+
if (tools.length > 0) {
|
|
2859
|
+
builder = builder.tools(tools);
|
|
2860
|
+
}
|
|
2861
|
+
if (config.buildRequest) {
|
|
2862
|
+
builder = config.buildRequest(builder);
|
|
2863
|
+
}
|
|
2864
|
+
const response = await config.client.create(
|
|
2865
|
+
builder.build(),
|
|
2866
|
+
config.requestOptions
|
|
2568
2867
|
);
|
|
2868
|
+
usage.llmCalls += 1;
|
|
2869
|
+
usage.inputTokens += response.usage.inputTokens;
|
|
2870
|
+
usage.outputTokens += response.usage.outputTokens;
|
|
2871
|
+
usage.totalTokens += response.usage.totalTokens;
|
|
2872
|
+
const toolCalls = getAllToolCalls(response);
|
|
2873
|
+
if (toolCalls.length === 0) {
|
|
2874
|
+
const assistantText = getAssistantText(response);
|
|
2875
|
+
if (assistantText) {
|
|
2876
|
+
history.push(createAssistantMessage(assistantText));
|
|
2877
|
+
}
|
|
2878
|
+
return {
|
|
2879
|
+
status: "complete",
|
|
2880
|
+
output: assistantText,
|
|
2881
|
+
response,
|
|
2882
|
+
usage,
|
|
2883
|
+
input: history,
|
|
2884
|
+
turnsUsed: turn + 1
|
|
2885
|
+
};
|
|
2886
|
+
}
|
|
2887
|
+
usage.toolCalls += toolCalls.length;
|
|
2888
|
+
history.push(
|
|
2889
|
+
assistantMessageWithToolCalls(getAssistantText(response), toolCalls)
|
|
2890
|
+
);
|
|
2891
|
+
if (!config.registry) {
|
|
2892
|
+
return {
|
|
2893
|
+
status: "waiting_for_tools",
|
|
2894
|
+
pendingToolCalls: toolCalls,
|
|
2895
|
+
response,
|
|
2896
|
+
usage,
|
|
2897
|
+
input: history,
|
|
2898
|
+
turnsUsed: turn + 1
|
|
2899
|
+
};
|
|
2900
|
+
}
|
|
2901
|
+
const results = await config.registry.executeAll(toolCalls);
|
|
2902
|
+
history.push(...config.registry.resultsToMessages(results));
|
|
2569
2903
|
}
|
|
2570
|
-
|
|
2571
|
-
modelId,
|
|
2572
|
-
options,
|
|
2573
|
-
resolveModelContext
|
|
2574
|
-
);
|
|
2575
|
-
const truncated = truncateMessagesByTokens(
|
|
2576
|
-
messages,
|
|
2577
|
-
budget.maxHistoryTokens
|
|
2578
|
-
);
|
|
2579
|
-
if (options.onContextTruncate && truncated.length < messages.length) {
|
|
2580
|
-
const info = {
|
|
2581
|
-
model: modelId,
|
|
2582
|
-
originalMessages: messages.length,
|
|
2583
|
-
keptMessages: truncated.length,
|
|
2584
|
-
maxHistoryTokens: budget.maxHistoryTokens,
|
|
2585
|
-
reservedOutputTokens: budget.reservedOutputTokens
|
|
2586
|
-
};
|
|
2587
|
-
options.onContextTruncate(info);
|
|
2588
|
-
}
|
|
2589
|
-
return messagesToInput(truncated);
|
|
2904
|
+
throw new AgentMaxTurnsError(maxTurns);
|
|
2590
2905
|
}
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
content: m.content,
|
|
2596
|
-
toolCalls: m.toolCalls,
|
|
2597
|
-
toolCallId: m.toolCallId
|
|
2598
|
-
}));
|
|
2906
|
+
|
|
2907
|
+
// src/sessions/types.ts
|
|
2908
|
+
function asSessionId(value) {
|
|
2909
|
+
return value;
|
|
2599
2910
|
}
|
|
2600
|
-
function
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
selectedSystem.shift();
|
|
2609
|
-
systemTokens = sumTokens(tokensByIndex, selectedSystem);
|
|
2911
|
+
function generateSessionId() {
|
|
2912
|
+
return crypto.randomUUID();
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
// src/sessions/stores/memory_store.ts
|
|
2916
|
+
var MemoryConversationStore = class {
|
|
2917
|
+
constructor() {
|
|
2918
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
2610
2919
|
}
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
);
|
|
2920
|
+
async load(id) {
|
|
2921
|
+
const state = this.sessions.get(id);
|
|
2922
|
+
if (!state) return null;
|
|
2923
|
+
return structuredClone(state);
|
|
2615
2924
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
2619
|
-
if (selected.has(i)) continue;
|
|
2620
|
-
const tokens = tokensByIndex[i];
|
|
2621
|
-
if (tokens <= remaining) {
|
|
2622
|
-
selected.add(i);
|
|
2623
|
-
remaining -= tokens;
|
|
2624
|
-
}
|
|
2925
|
+
async save(state) {
|
|
2926
|
+
this.sessions.set(state.id, structuredClone(state));
|
|
2625
2927
|
}
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
throw new ConfigError("No messages fit within maxHistoryTokens");
|
|
2928
|
+
async delete(id) {
|
|
2929
|
+
this.sessions.delete(id);
|
|
2629
2930
|
}
|
|
2630
|
-
|
|
2931
|
+
async list() {
|
|
2932
|
+
return Array.from(this.sessions.keys());
|
|
2933
|
+
}
|
|
2934
|
+
async close() {
|
|
2935
|
+
this.sessions.clear();
|
|
2936
|
+
}
|
|
2937
|
+
/**
|
|
2938
|
+
* Get the number of sessions in the store.
|
|
2939
|
+
* Useful for testing.
|
|
2940
|
+
*/
|
|
2941
|
+
get size() {
|
|
2942
|
+
return this.sessions.size;
|
|
2943
|
+
}
|
|
2944
|
+
};
|
|
2945
|
+
function createMemoryConversationStore() {
|
|
2946
|
+
return new MemoryConversationStore();
|
|
2631
2947
|
}
|
|
2632
|
-
|
|
2633
|
-
|
|
2948
|
+
|
|
2949
|
+
// src/sessions/stores/serialization.ts
|
|
2950
|
+
function serializeConversationState(state) {
|
|
2951
|
+
return {
|
|
2952
|
+
...state,
|
|
2953
|
+
messages: state.messages.map((message) => ({
|
|
2954
|
+
...message,
|
|
2955
|
+
createdAt: message.createdAt.toISOString()
|
|
2956
|
+
}))
|
|
2957
|
+
};
|
|
2634
2958
|
}
|
|
2635
|
-
function
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2959
|
+
function deserializeConversationState(state) {
|
|
2960
|
+
return {
|
|
2961
|
+
...state,
|
|
2962
|
+
messages: state.messages.map((message) => ({
|
|
2963
|
+
...message,
|
|
2964
|
+
createdAt: new Date(message.createdAt)
|
|
2965
|
+
}))
|
|
2966
|
+
};
|
|
2639
2967
|
}
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2968
|
+
|
|
2969
|
+
// src/sessions/stores/file_store.ts
|
|
2970
|
+
var DEFAULT_SESSION_DIR = ".modelrelay/sessions";
|
|
2971
|
+
async function loadNodeDeps() {
|
|
2972
|
+
try {
|
|
2973
|
+
const fs = await import("fs/promises");
|
|
2974
|
+
const path = await import("path");
|
|
2975
|
+
const os = await import("os");
|
|
2976
|
+
return { fs, path, os };
|
|
2977
|
+
} catch (err) {
|
|
2978
|
+
throw new ConfigError("file persistence requires a Node.js-compatible runtime");
|
|
2979
|
+
}
|
|
2644
2980
|
}
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
for (const part of message.content || []) {
|
|
2649
|
-
if (part.type === "text" && part.text) {
|
|
2650
|
-
segments.push(part.text);
|
|
2651
|
-
} else if (isImagePart(part)) {
|
|
2652
|
-
imageTokens += estimateImageTokens(part);
|
|
2653
|
-
}
|
|
2981
|
+
var FileConversationStore = class {
|
|
2982
|
+
constructor(storagePath) {
|
|
2983
|
+
this.storagePath = storagePath;
|
|
2654
2984
|
}
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2985
|
+
async load(id) {
|
|
2986
|
+
const { fs, path, os } = await loadNodeDeps();
|
|
2987
|
+
const filePath = await this.resolveSessionPath(id, path, os);
|
|
2988
|
+
try {
|
|
2989
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
2990
|
+
const parsed = JSON.parse(raw);
|
|
2991
|
+
return deserializeConversationState(parsed);
|
|
2992
|
+
} catch (err) {
|
|
2993
|
+
if (isNotFoundError(err)) return null;
|
|
2994
|
+
throw err;
|
|
2659
2995
|
}
|
|
2660
2996
|
}
|
|
2661
|
-
|
|
2662
|
-
|
|
2997
|
+
async save(state) {
|
|
2998
|
+
const { fs, path, os } = await loadNodeDeps();
|
|
2999
|
+
const dirPath = await this.resolveSessionDir(path, os);
|
|
3000
|
+
await fs.mkdir(dirPath, { recursive: true, mode: 448 });
|
|
3001
|
+
const filePath = path.join(dirPath, `${state.id}.json`);
|
|
3002
|
+
const payload = JSON.stringify(serializeConversationState(state), null, 2);
|
|
3003
|
+
await fs.writeFile(filePath, payload, { mode: 384 });
|
|
2663
3004
|
}
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
3005
|
+
async delete(id) {
|
|
3006
|
+
const { fs, path, os } = await loadNodeDeps();
|
|
3007
|
+
const filePath = await this.resolveSessionPath(id, path, os);
|
|
3008
|
+
try {
|
|
3009
|
+
await fs.unlink(filePath);
|
|
3010
|
+
} catch (err) {
|
|
3011
|
+
if (isNotFoundError(err)) return;
|
|
3012
|
+
throw err;
|
|
3013
|
+
}
|
|
2671
3014
|
}
|
|
2672
|
-
|
|
2673
|
-
}
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
if (options.maxHistoryTokens !== void 0) {
|
|
2683
|
-
return {
|
|
2684
|
-
maxHistoryTokens: normalizePositiveInt(
|
|
2685
|
-
options.maxHistoryTokens,
|
|
2686
|
-
"maxHistoryTokens"
|
|
2687
|
-
),
|
|
2688
|
-
reservedOutputTokens
|
|
2689
|
-
};
|
|
3015
|
+
async list() {
|
|
3016
|
+
const { fs, path, os } = await loadNodeDeps();
|
|
3017
|
+
const dirPath = await this.resolveSessionDir(path, os);
|
|
3018
|
+
try {
|
|
3019
|
+
const entries = await fs.readdir(dirPath);
|
|
3020
|
+
return entries.filter((entry) => path.extname(entry) === ".json").map((entry) => entry.replace(/\.json$/, ""));
|
|
3021
|
+
} catch (err) {
|
|
3022
|
+
if (isNotFoundError(err)) return [];
|
|
3023
|
+
throw err;
|
|
3024
|
+
}
|
|
2690
3025
|
}
|
|
2691
|
-
|
|
2692
|
-
if (!model) {
|
|
2693
|
-
throw new ConfigError(
|
|
2694
|
-
`Unknown model "${modelId}"; ensure the model exists in the ModelRelay catalog`
|
|
2695
|
-
);
|
|
3026
|
+
async close() {
|
|
2696
3027
|
}
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
const buffer = Math.max(
|
|
2701
|
-
DEFAULT_CONTEXT_BUFFER_TOKENS,
|
|
2702
|
-
Math.ceil(contextWindow * CONTEXT_BUFFER_RATIO)
|
|
2703
|
-
);
|
|
2704
|
-
const maxHistoryTokens = contextWindow - effectiveReserve - buffer;
|
|
2705
|
-
if (maxHistoryTokens <= 0) {
|
|
2706
|
-
throw new ConfigError(
|
|
2707
|
-
"model context window is too small after reserving output tokens; set maxHistoryTokens explicitly"
|
|
2708
|
-
);
|
|
3028
|
+
async resolveSessionPath(id, path, os) {
|
|
3029
|
+
const dirPath = await this.resolveSessionDir(path, os);
|
|
3030
|
+
return path.join(dirPath, `${id}.json`);
|
|
2709
3031
|
}
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
function normalizeNonNegativeInt(value, label) {
|
|
2716
|
-
if (!Number.isFinite(value) || value < 0) {
|
|
2717
|
-
throw new ConfigError(`${label} must be a non-negative number`);
|
|
3032
|
+
async resolveSessionDir(path, os) {
|
|
3033
|
+
if (this.storagePath && this.storagePath.trim()) {
|
|
3034
|
+
return this.storagePath;
|
|
3035
|
+
}
|
|
3036
|
+
return path.join(os.homedir(), DEFAULT_SESSION_DIR);
|
|
2718
3037
|
}
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
const existing = modelContextCache.get(client);
|
|
2723
|
-
if (existing) return existing;
|
|
2724
|
-
const entry = { byId: /* @__PURE__ */ new Map() };
|
|
2725
|
-
modelContextCache.set(client, entry);
|
|
2726
|
-
return entry;
|
|
3038
|
+
};
|
|
3039
|
+
function createFileConversationStore(storagePath) {
|
|
3040
|
+
return new FileConversationStore(storagePath);
|
|
2727
3041
|
}
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
for (const model of response.models) {
|
|
2733
|
-
entry.byId.set(String(model.model_id), {
|
|
2734
|
-
contextWindow: model.context_window,
|
|
2735
|
-
maxOutputTokens: model.max_output_tokens
|
|
2736
|
-
});
|
|
2737
|
-
}
|
|
2738
|
-
})().finally(() => {
|
|
2739
|
-
entry.listPromise = void 0;
|
|
2740
|
-
});
|
|
2741
|
-
}
|
|
2742
|
-
await entry.listPromise;
|
|
3042
|
+
function isNotFoundError(err) {
|
|
3043
|
+
return Boolean(
|
|
3044
|
+
err && typeof err === "object" && "code" in err && err.code === "ENOENT"
|
|
3045
|
+
);
|
|
2743
3046
|
}
|
|
2744
3047
|
|
|
2745
|
-
// src/sessions/
|
|
2746
|
-
|
|
2747
|
-
|
|
3048
|
+
// src/sessions/stores/sqlite_store.ts
|
|
3049
|
+
var DEFAULT_DB_PATH = ".modelrelay/sessions.sqlite";
|
|
3050
|
+
async function loadNodeDeps2() {
|
|
3051
|
+
try {
|
|
3052
|
+
const path = await import("path");
|
|
3053
|
+
const os = await import("os");
|
|
3054
|
+
return { path, os };
|
|
3055
|
+
} catch (err) {
|
|
3056
|
+
throw new ConfigError("sqlite persistence requires a Node.js-compatible runtime");
|
|
3057
|
+
}
|
|
2748
3058
|
}
|
|
2749
|
-
function
|
|
2750
|
-
|
|
3059
|
+
async function loadSqlite() {
|
|
3060
|
+
try {
|
|
3061
|
+
const mod = await import("better-sqlite3");
|
|
3062
|
+
const Database = mod.default ?? mod;
|
|
3063
|
+
if (typeof Database !== "function") {
|
|
3064
|
+
throw new Error("better-sqlite3 export missing");
|
|
3065
|
+
}
|
|
3066
|
+
return Database;
|
|
3067
|
+
} catch (err) {
|
|
3068
|
+
throw new ConfigError(
|
|
3069
|
+
"sqlite persistence requires the optional 'better-sqlite3' dependency"
|
|
3070
|
+
);
|
|
3071
|
+
}
|
|
2751
3072
|
}
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
constructor() {
|
|
2756
|
-
this.sessions = /* @__PURE__ */ new Map();
|
|
3073
|
+
var SqliteConversationStore = class {
|
|
3074
|
+
constructor(storagePath) {
|
|
3075
|
+
this.storagePath = storagePath;
|
|
2757
3076
|
}
|
|
2758
3077
|
async load(id) {
|
|
2759
|
-
const
|
|
2760
|
-
|
|
2761
|
-
|
|
3078
|
+
const statements = await this.getStatements();
|
|
3079
|
+
const row = statements.get.get({ id });
|
|
3080
|
+
if (!row) return null;
|
|
3081
|
+
const parsed = {
|
|
3082
|
+
id: row.id,
|
|
3083
|
+
messages: JSON.parse(row.messages),
|
|
3084
|
+
artifacts: JSON.parse(row.artifacts),
|
|
3085
|
+
metadata: JSON.parse(row.metadata),
|
|
3086
|
+
createdAt: row.createdAt,
|
|
3087
|
+
updatedAt: row.updatedAt
|
|
3088
|
+
};
|
|
3089
|
+
return deserializeConversationState(parsed);
|
|
2762
3090
|
}
|
|
2763
3091
|
async save(state) {
|
|
2764
|
-
this.
|
|
3092
|
+
const statements = await this.getStatements();
|
|
3093
|
+
const payload = serializeConversationState(state);
|
|
3094
|
+
statements.save.run({
|
|
3095
|
+
id: payload.id,
|
|
3096
|
+
messages: JSON.stringify(payload.messages),
|
|
3097
|
+
artifacts: JSON.stringify(payload.artifacts ?? {}),
|
|
3098
|
+
metadata: JSON.stringify(payload.metadata ?? {}),
|
|
3099
|
+
created_at: payload.createdAt,
|
|
3100
|
+
updated_at: payload.updatedAt
|
|
3101
|
+
});
|
|
2765
3102
|
}
|
|
2766
3103
|
async delete(id) {
|
|
2767
|
-
this.
|
|
3104
|
+
const statements = await this.getStatements();
|
|
3105
|
+
statements.delete.run({ id });
|
|
2768
3106
|
}
|
|
2769
3107
|
async list() {
|
|
2770
|
-
|
|
3108
|
+
const statements = await this.getStatements();
|
|
3109
|
+
const rows = statements.list.all();
|
|
3110
|
+
return rows.map((row) => row.id);
|
|
2771
3111
|
}
|
|
2772
3112
|
async close() {
|
|
2773
|
-
this.
|
|
3113
|
+
if (this.db) {
|
|
3114
|
+
this.db.close();
|
|
3115
|
+
this.db = void 0;
|
|
3116
|
+
this.statements = void 0;
|
|
3117
|
+
this.initPromise = void 0;
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
async ensureInitialized() {
|
|
3121
|
+
if (this.db) return;
|
|
3122
|
+
if (!this.initPromise) {
|
|
3123
|
+
this.initPromise = this.initialize();
|
|
3124
|
+
}
|
|
3125
|
+
await this.initPromise;
|
|
3126
|
+
}
|
|
3127
|
+
async getStatements() {
|
|
3128
|
+
await this.ensureInitialized();
|
|
3129
|
+
if (!this.statements) {
|
|
3130
|
+
throw new Error("Database initialization failed");
|
|
3131
|
+
}
|
|
3132
|
+
return this.statements;
|
|
3133
|
+
}
|
|
3134
|
+
async initialize() {
|
|
3135
|
+
const { path, os } = await loadNodeDeps2();
|
|
3136
|
+
const Database = await loadSqlite();
|
|
3137
|
+
const dbPath = this.resolveDbPath(path, os);
|
|
3138
|
+
this.db = new Database(dbPath);
|
|
3139
|
+
this.db.exec(`
|
|
3140
|
+
CREATE TABLE IF NOT EXISTS conversations (
|
|
3141
|
+
id TEXT PRIMARY KEY,
|
|
3142
|
+
messages TEXT NOT NULL,
|
|
3143
|
+
artifacts TEXT NOT NULL,
|
|
3144
|
+
metadata TEXT NOT NULL,
|
|
3145
|
+
created_at TEXT NOT NULL,
|
|
3146
|
+
updated_at TEXT NOT NULL
|
|
3147
|
+
)
|
|
3148
|
+
`);
|
|
3149
|
+
this.statements = {
|
|
3150
|
+
get: this.db.prepare(
|
|
3151
|
+
"SELECT id, messages, artifacts, metadata, created_at as createdAt, updated_at as updatedAt FROM conversations WHERE id = @id"
|
|
3152
|
+
),
|
|
3153
|
+
save: this.db.prepare(
|
|
3154
|
+
"INSERT INTO conversations (id, messages, artifacts, metadata, created_at, updated_at) VALUES (@id, @messages, @artifacts, @metadata, @created_at, @updated_at) ON CONFLICT(id) DO UPDATE SET messages = excluded.messages, artifacts = excluded.artifacts, metadata = excluded.metadata, updated_at = excluded.updated_at"
|
|
3155
|
+
),
|
|
3156
|
+
delete: this.db.prepare("DELETE FROM conversations WHERE id = @id"),
|
|
3157
|
+
list: this.db.prepare("SELECT id FROM conversations ORDER BY id")
|
|
3158
|
+
};
|
|
2774
3159
|
}
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
return this.sessions.size;
|
|
3160
|
+
resolveDbPath(path, os) {
|
|
3161
|
+
if (this.storagePath && this.storagePath.trim()) {
|
|
3162
|
+
return this.storagePath;
|
|
3163
|
+
}
|
|
3164
|
+
return path.join(os.homedir(), DEFAULT_DB_PATH);
|
|
2781
3165
|
}
|
|
2782
3166
|
};
|
|
2783
|
-
function
|
|
2784
|
-
return new
|
|
3167
|
+
function createSqliteConversationStore(storagePath) {
|
|
3168
|
+
return new SqliteConversationStore(storagePath);
|
|
3169
|
+
}
|
|
3170
|
+
|
|
3171
|
+
// src/sessions/utils.ts
|
|
3172
|
+
function messagesToInput(messages) {
|
|
3173
|
+
return messages.map((m) => ({
|
|
3174
|
+
type: m.type,
|
|
3175
|
+
role: m.role,
|
|
3176
|
+
content: m.content,
|
|
3177
|
+
toolCalls: m.toolCalls,
|
|
3178
|
+
toolCallId: m.toolCallId
|
|
3179
|
+
}));
|
|
3180
|
+
}
|
|
3181
|
+
function mergeTools(defaults, overrides) {
|
|
3182
|
+
if (!defaults && !overrides) return void 0;
|
|
3183
|
+
if (!defaults) return overrides;
|
|
3184
|
+
if (!overrides) return defaults;
|
|
3185
|
+
const merged = /* @__PURE__ */ new Map();
|
|
3186
|
+
for (const tool of defaults) {
|
|
3187
|
+
if (tool.type === "function" && tool.function) {
|
|
3188
|
+
merged.set(tool.function.name, tool);
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
for (const tool of overrides) {
|
|
3192
|
+
if (tool.type === "function" && tool.function) {
|
|
3193
|
+
merged.set(tool.function.name, tool);
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
return Array.from(merged.values());
|
|
3197
|
+
}
|
|
3198
|
+
function emptyUsage() {
|
|
3199
|
+
return {
|
|
3200
|
+
inputTokens: 0,
|
|
3201
|
+
outputTokens: 0,
|
|
3202
|
+
totalTokens: 0,
|
|
3203
|
+
llmCalls: 0,
|
|
3204
|
+
toolCalls: 0
|
|
3205
|
+
};
|
|
3206
|
+
}
|
|
3207
|
+
function createRequestBuilder(config) {
|
|
3208
|
+
return (builder) => {
|
|
3209
|
+
let next = builder;
|
|
3210
|
+
if (config.model) {
|
|
3211
|
+
next = next.model(config.model);
|
|
3212
|
+
}
|
|
3213
|
+
if (config.provider) {
|
|
3214
|
+
next = next.provider(config.provider);
|
|
3215
|
+
}
|
|
3216
|
+
if (config.customerId) {
|
|
3217
|
+
next = next.customerId(config.customerId);
|
|
3218
|
+
}
|
|
3219
|
+
return next;
|
|
3220
|
+
};
|
|
2785
3221
|
}
|
|
2786
3222
|
|
|
2787
3223
|
// src/sessions/local_session.ts
|
|
3224
|
+
var DEFAULT_MAX_TURNS2 = 100;
|
|
2788
3225
|
var LocalSession = class _LocalSession {
|
|
2789
3226
|
constructor(client, store, options, existingState) {
|
|
2790
3227
|
this.type = "local";
|
|
2791
3228
|
this.messages = [];
|
|
2792
3229
|
this.artifacts = /* @__PURE__ */ new Map();
|
|
2793
|
-
this.nextSeq = 1;
|
|
2794
|
-
this.currentEvents = [];
|
|
2795
|
-
this.currentUsage = {
|
|
2796
|
-
inputTokens: 0,
|
|
2797
|
-
outputTokens: 0,
|
|
2798
|
-
totalTokens: 0,
|
|
2799
|
-
llmCalls: 0,
|
|
2800
|
-
toolCalls: 0
|
|
2801
|
-
};
|
|
2802
3230
|
this.client = client;
|
|
2803
3231
|
this.store = store;
|
|
2804
3232
|
this.toolRegistry = options.toolRegistry;
|
|
3233
|
+
this.contextManager = options.contextManager;
|
|
2805
3234
|
this.defaultModel = options.defaultModel;
|
|
2806
3235
|
this.defaultProvider = options.defaultProvider;
|
|
2807
3236
|
this.defaultTools = options.defaultTools;
|
|
3237
|
+
this.systemPrompt = options.systemPrompt;
|
|
2808
3238
|
this.metadata = options.metadata || {};
|
|
2809
|
-
this.resolveModelContext = createModelContextResolver(client);
|
|
2810
3239
|
if (existingState) {
|
|
2811
3240
|
this.id = existingState.id;
|
|
2812
3241
|
this.messages = existingState.messages.map((m) => ({
|
|
@@ -2814,7 +3243,6 @@ var LocalSession = class _LocalSession {
|
|
|
2814
3243
|
createdAt: new Date(m.createdAt)
|
|
2815
3244
|
}));
|
|
2816
3245
|
this.artifacts = new Map(Object.entries(existingState.artifacts));
|
|
2817
|
-
this.nextSeq = this.messages.length + 1;
|
|
2818
3246
|
this.createdAt = new Date(existingState.createdAt);
|
|
2819
3247
|
this.updatedAt = new Date(existingState.updatedAt);
|
|
2820
3248
|
} else {
|
|
@@ -2831,7 +3259,11 @@ var LocalSession = class _LocalSession {
|
|
|
2831
3259
|
* @returns A new LocalSession instance
|
|
2832
3260
|
*/
|
|
2833
3261
|
static create(client, options = {}) {
|
|
2834
|
-
const store = createStore(
|
|
3262
|
+
const store = createStore(
|
|
3263
|
+
options.conversationStore,
|
|
3264
|
+
options.persistence || "memory",
|
|
3265
|
+
options.storagePath
|
|
3266
|
+
);
|
|
2835
3267
|
return new _LocalSession(client, store, options);
|
|
2836
3268
|
}
|
|
2837
3269
|
/**
|
|
@@ -2844,7 +3276,11 @@ var LocalSession = class _LocalSession {
|
|
|
2844
3276
|
*/
|
|
2845
3277
|
static async resume(client, sessionId, options = {}) {
|
|
2846
3278
|
const id = typeof sessionId === "string" ? asSessionId(sessionId) : sessionId;
|
|
2847
|
-
const store = createStore(
|
|
3279
|
+
const store = createStore(
|
|
3280
|
+
options.conversationStore,
|
|
3281
|
+
options.persistence || "memory",
|
|
3282
|
+
options.storagePath
|
|
3283
|
+
);
|
|
2848
3284
|
const state = await store.load(id);
|
|
2849
3285
|
if (!state) {
|
|
2850
3286
|
await store.close();
|
|
@@ -2856,106 +3292,147 @@ var LocalSession = class _LocalSession {
|
|
|
2856
3292
|
return this.messages;
|
|
2857
3293
|
}
|
|
2858
3294
|
async run(prompt, options = {}) {
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
this.currentEvents = [];
|
|
2865
|
-
this.currentUsage = {
|
|
2866
|
-
inputTokens: 0,
|
|
2867
|
-
outputTokens: 0,
|
|
2868
|
-
totalTokens: 0,
|
|
2869
|
-
llmCalls: 0,
|
|
2870
|
-
toolCalls: 0
|
|
2871
|
-
};
|
|
2872
|
-
this.currentRunId = void 0;
|
|
2873
|
-
this.currentNodeId = void 0;
|
|
2874
|
-
this.currentWaiting = void 0;
|
|
3295
|
+
this.pendingLoop = void 0;
|
|
3296
|
+
this.messages.push(buildMessage(createUserMessage(prompt), this.messages.length + 1));
|
|
3297
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
3298
|
+
const baseInput = messagesToInput(this.messages);
|
|
3299
|
+
const contextOptions = this.buildContextOptions(options);
|
|
2875
3300
|
try {
|
|
2876
|
-
const
|
|
3301
|
+
const prepared = await this.prepareInput(baseInput, contextOptions);
|
|
2877
3302
|
const tools = mergeTools(this.defaultTools, options.tools);
|
|
2878
|
-
const
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
3303
|
+
const modelId = options.model ?? this.defaultModel;
|
|
3304
|
+
const providerId = options.provider ?? this.defaultProvider;
|
|
3305
|
+
const requestOptions = options.signal ? { signal: options.signal } : {};
|
|
3306
|
+
const outcome = await runToolLoop({
|
|
3307
|
+
client: this.client.responses,
|
|
3308
|
+
input: prepared,
|
|
3309
|
+
tools,
|
|
3310
|
+
registry: this.toolRegistry,
|
|
3311
|
+
maxTurns: options.maxTurns ?? DEFAULT_MAX_TURNS2,
|
|
3312
|
+
requestOptions,
|
|
3313
|
+
buildRequest: createRequestBuilder({
|
|
3314
|
+
model: modelId,
|
|
3315
|
+
provider: providerId,
|
|
3316
|
+
customerId: options.customerId
|
|
3317
|
+
})
|
|
3318
|
+
});
|
|
3319
|
+
const cleanInput = stripSystemPrompt(outcome.input, this.systemPrompt);
|
|
3320
|
+
this.replaceHistory(cleanInput);
|
|
3321
|
+
await this.persist();
|
|
3322
|
+
const usage = outcome.usage;
|
|
3323
|
+
if (outcome.status === "waiting_for_tools") {
|
|
3324
|
+
const pendingRequestOptions = { ...requestOptions };
|
|
3325
|
+
delete pendingRequestOptions.signal;
|
|
3326
|
+
this.pendingLoop = {
|
|
3327
|
+
input: cleanInput,
|
|
3328
|
+
usage,
|
|
3329
|
+
remainingTurns: remainingTurns(
|
|
3330
|
+
options.maxTurns ?? DEFAULT_MAX_TURNS2,
|
|
3331
|
+
outcome.turnsUsed
|
|
3332
|
+
),
|
|
3333
|
+
config: {
|
|
3334
|
+
model: modelId,
|
|
3335
|
+
provider: providerId,
|
|
2887
3336
|
tools,
|
|
2888
|
-
|
|
3337
|
+
customerId: options.customerId,
|
|
3338
|
+
requestOptions: pendingRequestOptions,
|
|
3339
|
+
contextOptions
|
|
2889
3340
|
}
|
|
2890
|
-
|
|
2891
|
-
|
|
3341
|
+
};
|
|
3342
|
+
return {
|
|
3343
|
+
status: "waiting_for_tools",
|
|
3344
|
+
pendingTools: mapPendingToolCalls(outcome.pendingToolCalls),
|
|
3345
|
+
response: outcome.response,
|
|
3346
|
+
usage
|
|
3347
|
+
};
|
|
3348
|
+
}
|
|
3349
|
+
return {
|
|
3350
|
+
status: "complete",
|
|
3351
|
+
output: outcome.output,
|
|
3352
|
+
response: outcome.response,
|
|
3353
|
+
usage
|
|
2892
3354
|
};
|
|
2893
|
-
const run = await this.client.runs.create(spec, {
|
|
2894
|
-
customerId: options.customerId
|
|
2895
|
-
});
|
|
2896
|
-
this.currentRunId = run.run_id;
|
|
2897
|
-
return await this.processRunEvents(options.signal);
|
|
2898
3355
|
} catch (err) {
|
|
2899
3356
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
2900
3357
|
return {
|
|
2901
3358
|
status: "error",
|
|
2902
3359
|
error: error.message,
|
|
2903
|
-
|
|
2904
|
-
usage:
|
|
2905
|
-
events: this.currentEvents
|
|
3360
|
+
cause: error,
|
|
3361
|
+
usage: emptyUsage()
|
|
2906
3362
|
};
|
|
2907
3363
|
}
|
|
2908
3364
|
}
|
|
2909
3365
|
async submitToolResults(results) {
|
|
2910
|
-
if (!this.
|
|
3366
|
+
if (!this.pendingLoop) {
|
|
2911
3367
|
throw new Error("No pending tool calls to submit results for");
|
|
2912
3368
|
}
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
},
|
|
2922
|
-
output: r.error ? `Error: ${r.error}` : typeof r.result === "string" ? r.result : JSON.stringify(r.result)
|
|
2923
|
-
}))
|
|
3369
|
+
const pending = this.pendingLoop;
|
|
3370
|
+
this.pendingLoop = void 0;
|
|
3371
|
+
if (pending.remainingTurns <= 0) {
|
|
3372
|
+
throw new AgentMaxTurnsError(0);
|
|
3373
|
+
}
|
|
3374
|
+
const resultItems = results.map((result) => {
|
|
3375
|
+
const content = result.error ? `Error: ${result.error}` : typeof result.result === "string" ? result.result : JSON.stringify(result.result);
|
|
3376
|
+
return toolResultMessage(result.toolCallId, content);
|
|
2924
3377
|
});
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
3378
|
+
const baseInput = [...pending.input, ...resultItems];
|
|
3379
|
+
try {
|
|
3380
|
+
const prepared = await this.prepareInput(baseInput, pending.config.contextOptions);
|
|
3381
|
+
const outcome = await runToolLoop({
|
|
3382
|
+
client: this.client.responses,
|
|
3383
|
+
input: prepared,
|
|
3384
|
+
tools: pending.config.tools,
|
|
3385
|
+
registry: this.toolRegistry,
|
|
3386
|
+
maxTurns: pending.remainingTurns,
|
|
3387
|
+
requestOptions: pending.config.requestOptions,
|
|
3388
|
+
buildRequest: createRequestBuilder(pending.config)
|
|
3389
|
+
});
|
|
3390
|
+
const cleanInput = stripSystemPrompt(outcome.input, this.systemPrompt);
|
|
3391
|
+
this.replaceHistory(cleanInput);
|
|
3392
|
+
await this.persist();
|
|
3393
|
+
const usage = mergeUsage(pending.usage, outcome.usage);
|
|
3394
|
+
if (outcome.status === "waiting_for_tools") {
|
|
3395
|
+
this.pendingLoop = {
|
|
3396
|
+
input: cleanInput,
|
|
3397
|
+
usage,
|
|
3398
|
+
remainingTurns: remainingTurns(
|
|
3399
|
+
pending.remainingTurns,
|
|
3400
|
+
outcome.turnsUsed
|
|
3401
|
+
),
|
|
3402
|
+
config: pending.config
|
|
3403
|
+
};
|
|
3404
|
+
return {
|
|
3405
|
+
status: "waiting_for_tools",
|
|
3406
|
+
pendingTools: mapPendingToolCalls(outcome.pendingToolCalls),
|
|
3407
|
+
response: outcome.response,
|
|
3408
|
+
usage
|
|
3409
|
+
};
|
|
3410
|
+
}
|
|
3411
|
+
return {
|
|
3412
|
+
status: "complete",
|
|
3413
|
+
output: outcome.output,
|
|
3414
|
+
response: outcome.response,
|
|
3415
|
+
usage
|
|
3416
|
+
};
|
|
3417
|
+
} catch (err) {
|
|
3418
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
3419
|
+
return {
|
|
3420
|
+
status: "error",
|
|
3421
|
+
error: error.message,
|
|
3422
|
+
cause: error,
|
|
3423
|
+
usage: pending.usage
|
|
3424
|
+
};
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
getArtifacts() {
|
|
3428
|
+
return new Map(this.artifacts);
|
|
3429
|
+
}
|
|
2931
3430
|
async close() {
|
|
2932
3431
|
await this.persist();
|
|
2933
3432
|
await this.store.close();
|
|
2934
3433
|
}
|
|
2935
3434
|
/**
|
|
2936
3435
|
* Sync this local session's messages to a remote session.
|
|
2937
|
-
*
|
|
2938
|
-
* This uploads all local messages to the remote session, enabling
|
|
2939
|
-
* cross-device access and server-side backup. Messages are synced
|
|
2940
|
-
* in order and the remote session's history will contain all local
|
|
2941
|
-
* messages after sync completes.
|
|
2942
|
-
*
|
|
2943
|
-
* @param remoteSession - The remote session to sync to
|
|
2944
|
-
* @param options - Optional sync configuration
|
|
2945
|
-
* @returns Sync result with message count
|
|
2946
|
-
*
|
|
2947
|
-
* @example
|
|
2948
|
-
* ```typescript
|
|
2949
|
-
* // Create local session and work offline
|
|
2950
|
-
* const local = LocalSession.create(client, { ... });
|
|
2951
|
-
* await local.run("Implement the feature");
|
|
2952
|
-
*
|
|
2953
|
-
* // Later, sync to remote for backup/sharing
|
|
2954
|
-
* const remote = await RemoteSession.create(client);
|
|
2955
|
-
* const result = await local.syncTo(remote, {
|
|
2956
|
-
* onProgress: (synced, total) => console.log(`${synced}/${total}`),
|
|
2957
|
-
* });
|
|
2958
|
-
* ```
|
|
2959
3436
|
*/
|
|
2960
3437
|
async syncTo(remoteSession, options = {}) {
|
|
2961
3438
|
const { onProgress, signal } = options;
|
|
@@ -2997,150 +3474,35 @@ var LocalSession = class _LocalSession {
|
|
|
2997
3474
|
// ============================================================================
|
|
2998
3475
|
// Private Methods
|
|
2999
3476
|
// ============================================================================
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
seq: this.nextSeq++,
|
|
3004
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
3005
|
-
runId
|
|
3006
|
-
};
|
|
3007
|
-
this.messages.push(message);
|
|
3008
|
-
this.updatedAt = /* @__PURE__ */ new Date();
|
|
3009
|
-
return message;
|
|
3010
|
-
}
|
|
3011
|
-
async buildInput(options) {
|
|
3012
|
-
return buildSessionInputWithContext(
|
|
3013
|
-
this.messages,
|
|
3014
|
-
options,
|
|
3015
|
-
this.defaultModel,
|
|
3016
|
-
this.resolveModelContext
|
|
3017
|
-
);
|
|
3018
|
-
}
|
|
3019
|
-
async processRunEvents(signal) {
|
|
3020
|
-
if (!this.currentRunId) {
|
|
3021
|
-
throw new Error("No current run");
|
|
3022
|
-
}
|
|
3023
|
-
const eventStream = await this.client.runs.events(this.currentRunId, {
|
|
3024
|
-
afterSeq: this.currentEvents.length
|
|
3025
|
-
});
|
|
3026
|
-
for await (const event of eventStream) {
|
|
3027
|
-
if (signal?.aborted) {
|
|
3028
|
-
return {
|
|
3029
|
-
status: "canceled",
|
|
3030
|
-
runId: this.currentRunId,
|
|
3031
|
-
usage: this.currentUsage,
|
|
3032
|
-
events: this.currentEvents
|
|
3033
|
-
};
|
|
3034
|
-
}
|
|
3035
|
-
this.currentEvents.push(event);
|
|
3036
|
-
switch (event.type) {
|
|
3037
|
-
case "node_llm_call":
|
|
3038
|
-
this.currentUsage = {
|
|
3039
|
-
...this.currentUsage,
|
|
3040
|
-
llmCalls: this.currentUsage.llmCalls + 1,
|
|
3041
|
-
inputTokens: this.currentUsage.inputTokens + (event.llm_call.usage?.input_tokens || 0),
|
|
3042
|
-
outputTokens: this.currentUsage.outputTokens + (event.llm_call.usage?.output_tokens || 0),
|
|
3043
|
-
totalTokens: this.currentUsage.totalTokens + (event.llm_call.usage?.total_tokens || 0)
|
|
3044
|
-
};
|
|
3045
|
-
break;
|
|
3046
|
-
case "node_tool_call":
|
|
3047
|
-
this.currentUsage = {
|
|
3048
|
-
...this.currentUsage,
|
|
3049
|
-
toolCalls: this.currentUsage.toolCalls + 1
|
|
3050
|
-
};
|
|
3051
|
-
break;
|
|
3052
|
-
case "node_waiting":
|
|
3053
|
-
this.currentNodeId = event.node_id;
|
|
3054
|
-
this.currentWaiting = event.waiting;
|
|
3055
|
-
if (this.toolRegistry) {
|
|
3056
|
-
const results = await this.executeTools(event.waiting.pending_tool_calls);
|
|
3057
|
-
return await this.submitToolResults(results);
|
|
3058
|
-
}
|
|
3059
|
-
return {
|
|
3060
|
-
status: "waiting_for_tools",
|
|
3061
|
-
pendingTools: event.waiting.pending_tool_calls.map((tc) => ({
|
|
3062
|
-
toolCallId: tc.tool_call.id,
|
|
3063
|
-
name: tc.tool_call.name,
|
|
3064
|
-
arguments: tc.tool_call.arguments
|
|
3065
|
-
})),
|
|
3066
|
-
runId: this.currentRunId,
|
|
3067
|
-
usage: this.currentUsage,
|
|
3068
|
-
events: this.currentEvents
|
|
3069
|
-
};
|
|
3070
|
-
case "run_completed":
|
|
3071
|
-
const runState = await this.client.runs.get(this.currentRunId);
|
|
3072
|
-
const output = extractTextOutput(runState.outputs || {});
|
|
3073
|
-
if (output) {
|
|
3074
|
-
this.addMessage(
|
|
3075
|
-
{
|
|
3076
|
-
type: "message",
|
|
3077
|
-
role: "assistant",
|
|
3078
|
-
content: [{ type: "text", text: output }]
|
|
3079
|
-
},
|
|
3080
|
-
this.currentRunId
|
|
3081
|
-
);
|
|
3082
|
-
}
|
|
3083
|
-
await this.persist();
|
|
3084
|
-
return {
|
|
3085
|
-
status: "complete",
|
|
3086
|
-
output,
|
|
3087
|
-
runId: this.currentRunId,
|
|
3088
|
-
usage: this.currentUsage,
|
|
3089
|
-
events: this.currentEvents
|
|
3090
|
-
};
|
|
3091
|
-
case "run_failed":
|
|
3092
|
-
return {
|
|
3093
|
-
status: "error",
|
|
3094
|
-
error: event.error.message,
|
|
3095
|
-
runId: this.currentRunId,
|
|
3096
|
-
usage: this.currentUsage,
|
|
3097
|
-
events: this.currentEvents
|
|
3098
|
-
};
|
|
3099
|
-
case "run_canceled":
|
|
3100
|
-
return {
|
|
3101
|
-
status: "canceled",
|
|
3102
|
-
error: event.error.message,
|
|
3103
|
-
runId: this.currentRunId,
|
|
3104
|
-
usage: this.currentUsage,
|
|
3105
|
-
events: this.currentEvents
|
|
3106
|
-
};
|
|
3107
|
-
}
|
|
3108
|
-
}
|
|
3477
|
+
buildContextOptions(options) {
|
|
3478
|
+
if (!this.contextManager) return null;
|
|
3479
|
+
if (options.contextManagement === "none") return null;
|
|
3109
3480
|
return {
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3481
|
+
model: options.model ?? this.defaultModel,
|
|
3482
|
+
strategy: options.contextManagement,
|
|
3483
|
+
maxHistoryTokens: options.maxHistoryTokens,
|
|
3484
|
+
reserveOutputTokens: options.reserveOutputTokens,
|
|
3485
|
+
onTruncate: options.onContextTruncate
|
|
3115
3486
|
};
|
|
3116
3487
|
}
|
|
3117
|
-
async
|
|
3118
|
-
|
|
3119
|
-
|
|
3488
|
+
async prepareInput(input, contextOptions) {
|
|
3489
|
+
let prepared = input;
|
|
3490
|
+
if (this.systemPrompt) {
|
|
3491
|
+
prepared = [createSystemMessage(this.systemPrompt), ...prepared];
|
|
3120
3492
|
}
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
try {
|
|
3124
|
-
const result = await this.toolRegistry.execute({
|
|
3125
|
-
id: pending.tool_call.id,
|
|
3126
|
-
type: "function",
|
|
3127
|
-
function: {
|
|
3128
|
-
name: pending.tool_call.name,
|
|
3129
|
-
arguments: pending.tool_call.arguments
|
|
3130
|
-
}
|
|
3131
|
-
});
|
|
3132
|
-
results.push(result);
|
|
3133
|
-
} catch (err) {
|
|
3134
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
3135
|
-
results.push({
|
|
3136
|
-
toolCallId: pending.tool_call.id,
|
|
3137
|
-
toolName: pending.tool_call.name,
|
|
3138
|
-
result: null,
|
|
3139
|
-
error: error.message
|
|
3140
|
-
});
|
|
3141
|
-
}
|
|
3493
|
+
if (!this.contextManager || !contextOptions) {
|
|
3494
|
+
return prepared;
|
|
3142
3495
|
}
|
|
3143
|
-
return
|
|
3496
|
+
return this.contextManager.prepare(prepared, contextOptions);
|
|
3497
|
+
}
|
|
3498
|
+
replaceHistory(input) {
|
|
3499
|
+
const now = /* @__PURE__ */ new Date();
|
|
3500
|
+
this.messages = input.map((item, idx) => ({
|
|
3501
|
+
...item,
|
|
3502
|
+
seq: idx + 1,
|
|
3503
|
+
createdAt: now
|
|
3504
|
+
}));
|
|
3505
|
+
this.updatedAt = now;
|
|
3144
3506
|
}
|
|
3145
3507
|
async persist() {
|
|
3146
3508
|
const state = {
|
|
@@ -3157,159 +3519,373 @@ var LocalSession = class _LocalSession {
|
|
|
3157
3519
|
await this.store.save(state);
|
|
3158
3520
|
}
|
|
3159
3521
|
};
|
|
3160
|
-
function createStore(persistence, storagePath) {
|
|
3522
|
+
function createStore(custom, persistence, storagePath) {
|
|
3523
|
+
if (custom) {
|
|
3524
|
+
return custom;
|
|
3525
|
+
}
|
|
3161
3526
|
switch (persistence) {
|
|
3162
3527
|
case "memory":
|
|
3163
|
-
return
|
|
3528
|
+
return createMemoryConversationStore();
|
|
3164
3529
|
case "file":
|
|
3165
|
-
|
|
3530
|
+
return createFileConversationStore(storagePath);
|
|
3166
3531
|
case "sqlite":
|
|
3167
|
-
|
|
3532
|
+
return createSqliteConversationStore(storagePath);
|
|
3168
3533
|
default:
|
|
3169
3534
|
throw new Error(`Unknown persistence mode: ${persistence}`);
|
|
3170
3535
|
}
|
|
3171
3536
|
}
|
|
3172
|
-
function
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3537
|
+
function buildMessage(item, seq) {
|
|
3538
|
+
return {
|
|
3539
|
+
...item,
|
|
3540
|
+
seq,
|
|
3541
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
3542
|
+
};
|
|
3543
|
+
}
|
|
3544
|
+
function stripSystemPrompt(input, systemPrompt) {
|
|
3545
|
+
if (!systemPrompt || input.length === 0) {
|
|
3546
|
+
return input;
|
|
3181
3547
|
}
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
}
|
|
3548
|
+
const [first, ...rest] = input;
|
|
3549
|
+
if (first.role === "system" && first.content?.length === 1 && first.content[0].type === "text" && first.content[0].text === systemPrompt) {
|
|
3550
|
+
return rest;
|
|
3186
3551
|
}
|
|
3187
|
-
return
|
|
3552
|
+
return input;
|
|
3188
3553
|
}
|
|
3189
|
-
function
|
|
3190
|
-
return
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
return typeof c === "object" && c !== null && "type" in c && typeof c.type === "string";
|
|
3194
|
-
}
|
|
3195
|
-
function hasOutputArray(obj) {
|
|
3196
|
-
return "output" in obj && Array.isArray(obj.output);
|
|
3197
|
-
}
|
|
3198
|
-
function hasContentArray(obj) {
|
|
3199
|
-
return "content" in obj && Array.isArray(obj.content);
|
|
3200
|
-
}
|
|
3201
|
-
function extractTextOutput(outputs) {
|
|
3202
|
-
const result = outputs.result;
|
|
3203
|
-
if (typeof result === "string") return result;
|
|
3204
|
-
if (result && typeof result === "object") {
|
|
3205
|
-
if (hasOutputArray(result)) {
|
|
3206
|
-
const textParts = result.output.filter(
|
|
3207
|
-
(item) => isOutputMessage(item) && item.type === "message" && item.role === "assistant"
|
|
3208
|
-
).flatMap(
|
|
3209
|
-
(item) => (item.content || []).filter((c) => isContentPiece(c) && c.type === "text").map((c) => c.text ?? "")
|
|
3210
|
-
).filter((text) => text.length > 0);
|
|
3211
|
-
if (textParts.length > 0) {
|
|
3212
|
-
return textParts.join("\n");
|
|
3213
|
-
}
|
|
3214
|
-
}
|
|
3215
|
-
if (hasContentArray(result)) {
|
|
3216
|
-
const textParts = result.content.filter((c) => isContentPiece(c) && c.type === "text").map((c) => c.text ?? "").filter((text) => text.length > 0);
|
|
3217
|
-
if (textParts.length > 0) {
|
|
3218
|
-
return textParts.join("\n");
|
|
3219
|
-
}
|
|
3554
|
+
function mapPendingToolCalls(calls) {
|
|
3555
|
+
return calls.map((call) => {
|
|
3556
|
+
if (!call.function?.name) {
|
|
3557
|
+
throw new Error(`Tool call ${call.id} missing function name`);
|
|
3220
3558
|
}
|
|
3559
|
+
return {
|
|
3560
|
+
toolCallId: call.id,
|
|
3561
|
+
name: call.function.name,
|
|
3562
|
+
arguments: call.function.arguments ?? "{}"
|
|
3563
|
+
};
|
|
3564
|
+
});
|
|
3565
|
+
}
|
|
3566
|
+
function remainingTurns(maxTurns, turnsUsed) {
|
|
3567
|
+
if (maxTurns === Number.MAX_SAFE_INTEGER) {
|
|
3568
|
+
return maxTurns;
|
|
3221
3569
|
}
|
|
3222
|
-
return
|
|
3570
|
+
return Math.max(0, maxTurns - turnsUsed);
|
|
3571
|
+
}
|
|
3572
|
+
function mergeUsage(base, add) {
|
|
3573
|
+
return {
|
|
3574
|
+
inputTokens: base.inputTokens + add.inputTokens,
|
|
3575
|
+
outputTokens: base.outputTokens + add.outputTokens,
|
|
3576
|
+
totalTokens: base.totalTokens + add.totalTokens,
|
|
3577
|
+
llmCalls: base.llmCalls + add.llmCalls,
|
|
3578
|
+
toolCalls: base.toolCalls + add.toolCalls
|
|
3579
|
+
};
|
|
3223
3580
|
}
|
|
3224
3581
|
function createLocalSession(client, options = {}) {
|
|
3225
3582
|
return LocalSession.create(client, options);
|
|
3226
3583
|
}
|
|
3227
3584
|
|
|
3228
|
-
// src/
|
|
3229
|
-
var
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
this.toolRegistry = options.toolRegistry;
|
|
3252
|
-
this.defaultModel = options.defaultModel;
|
|
3253
|
-
this.defaultProvider = options.defaultProvider;
|
|
3254
|
-
this.defaultTools = options.defaultTools;
|
|
3255
|
-
this.resolveModelContext = createModelContextResolver(client);
|
|
3256
|
-
if ("messages" in sessionData && sessionData.messages) {
|
|
3257
|
-
this.messages = sessionData.messages.map((m) => ({
|
|
3258
|
-
type: "message",
|
|
3259
|
-
role: m.role,
|
|
3260
|
-
content: m.content,
|
|
3261
|
-
seq: m.seq,
|
|
3262
|
-
createdAt: new Date(m.created_at),
|
|
3263
|
-
runId: m.run_id ? parseRunId(m.run_id) : void 0
|
|
3264
|
-
}));
|
|
3265
|
-
this.nextSeq = this.messages.length + 1;
|
|
3585
|
+
// src/context_manager.ts
|
|
3586
|
+
var DEFAULT_CONTEXT_BUFFER_TOKENS = 256;
|
|
3587
|
+
var CONTEXT_BUFFER_RATIO = 0.02;
|
|
3588
|
+
var MESSAGE_OVERHEAD_TOKENS = 6;
|
|
3589
|
+
var TOOL_CALL_OVERHEAD_TOKENS = 4;
|
|
3590
|
+
var CHARS_PER_TOKEN = 4;
|
|
3591
|
+
var IMAGE_TOKENS_LOW_DETAIL = 85;
|
|
3592
|
+
var IMAGE_TOKENS_HIGH_DETAIL = 1e3;
|
|
3593
|
+
var modelContextCache = /* @__PURE__ */ new WeakMap();
|
|
3594
|
+
function createModelContextResolver(client) {
|
|
3595
|
+
return async (modelId) => {
|
|
3596
|
+
const entry = getModelContextCacheEntry(client);
|
|
3597
|
+
const key = String(modelId);
|
|
3598
|
+
const cached = entry.byId.get(key);
|
|
3599
|
+
if (cached !== void 0) {
|
|
3600
|
+
return cached;
|
|
3601
|
+
}
|
|
3602
|
+
await populateModelContextCache(client, entry);
|
|
3603
|
+
const resolved = entry.byId.get(key);
|
|
3604
|
+
if (resolved === void 0) {
|
|
3605
|
+
throw new ConfigError(
|
|
3606
|
+
`Unknown model "${key}"; ensure the model exists in the ModelRelay catalog`
|
|
3607
|
+
);
|
|
3266
3608
|
}
|
|
3609
|
+
return resolved;
|
|
3610
|
+
};
|
|
3611
|
+
}
|
|
3612
|
+
var ContextManager = class {
|
|
3613
|
+
constructor(resolveModelContext, defaults = {}) {
|
|
3614
|
+
this.resolveModelContext = resolveModelContext;
|
|
3615
|
+
this.defaults = defaults;
|
|
3616
|
+
}
|
|
3617
|
+
async prepare(input, options = {}) {
|
|
3618
|
+
const merged = {
|
|
3619
|
+
...this.defaults,
|
|
3620
|
+
...options
|
|
3621
|
+
};
|
|
3622
|
+
return prepareInputWithContext(input, merged, this.resolveModelContext);
|
|
3267
3623
|
}
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
* @returns A new RemoteSession instance
|
|
3274
|
-
*/
|
|
3275
|
-
static async create(client, options = {}) {
|
|
3276
|
-
const http = getHTTPClient(client);
|
|
3277
|
-
const response = await http.request("/sessions", {
|
|
3278
|
-
method: "POST",
|
|
3279
|
-
body: {
|
|
3280
|
-
customer_id: options.customerId,
|
|
3281
|
-
metadata: options.metadata || {}
|
|
3282
|
-
}
|
|
3283
|
-
});
|
|
3284
|
-
const data = await response.json();
|
|
3285
|
-
return new _RemoteSession(client, http, data, options);
|
|
3624
|
+
};
|
|
3625
|
+
async function prepareInputWithContext(input, options, resolveModelContext) {
|
|
3626
|
+
const strategy = options.strategy ?? "truncate";
|
|
3627
|
+
if (strategy === "summarize") {
|
|
3628
|
+
throw new ConfigError("context management 'summarize' is not implemented yet");
|
|
3286
3629
|
}
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
*
|
|
3290
|
-
* @param client - ModelRelay client
|
|
3291
|
-
* @param sessionId - ID of the session to retrieve
|
|
3292
|
-
* @param options - Optional configuration (toolRegistry, defaults)
|
|
3293
|
-
* @returns The RemoteSession instance
|
|
3294
|
-
*/
|
|
3295
|
-
static async get(client, sessionId, options = {}) {
|
|
3296
|
-
const http = getHTTPClient(client);
|
|
3297
|
-
const id = typeof sessionId === "string" ? sessionId : String(sessionId);
|
|
3298
|
-
const response = await http.request(`/sessions/${id}`, {
|
|
3299
|
-
method: "GET"
|
|
3300
|
-
});
|
|
3301
|
-
const data = await response.json();
|
|
3302
|
-
return new _RemoteSession(client, http, data, options);
|
|
3630
|
+
if (strategy !== "truncate") {
|
|
3631
|
+
throw new ConfigError(`Unknown context management strategy: ${strategy}`);
|
|
3303
3632
|
}
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3633
|
+
const budget = await resolveHistoryBudget(
|
|
3634
|
+
options.model,
|
|
3635
|
+
options,
|
|
3636
|
+
resolveModelContext
|
|
3637
|
+
);
|
|
3638
|
+
const truncated = truncateInputByTokens(input, budget.maxHistoryTokens);
|
|
3639
|
+
if (options.onTruncate && truncated.length < input.length) {
|
|
3640
|
+
if (!options.model) {
|
|
3641
|
+
throw new ConfigError(
|
|
3642
|
+
"model is required for context management; set options.model"
|
|
3643
|
+
);
|
|
3644
|
+
}
|
|
3645
|
+
const info = {
|
|
3646
|
+
model: options.model,
|
|
3647
|
+
originalMessages: input.length,
|
|
3648
|
+
keptMessages: truncated.length,
|
|
3649
|
+
maxHistoryTokens: budget.maxHistoryTokens,
|
|
3650
|
+
reservedOutputTokens: budget.reservedOutputTokens
|
|
3651
|
+
};
|
|
3652
|
+
options.onTruncate(info);
|
|
3653
|
+
}
|
|
3654
|
+
return truncated;
|
|
3655
|
+
}
|
|
3656
|
+
function truncateInputByTokens(input, maxHistoryTokens) {
|
|
3657
|
+
const maxTokens = normalizePositiveInt(maxHistoryTokens, "maxHistoryTokens");
|
|
3658
|
+
if (input.length === 0) return [];
|
|
3659
|
+
const tokensByIndex = input.map((msg) => estimateTokensForMessage(msg));
|
|
3660
|
+
const systemIndices = input.map((msg, idx) => msg.role === "system" ? idx : -1).filter((idx) => idx >= 0);
|
|
3661
|
+
let selectedSystem = [...systemIndices];
|
|
3662
|
+
let systemTokens = sumTokens(tokensByIndex, selectedSystem);
|
|
3663
|
+
while (systemTokens > maxTokens && selectedSystem.length > 1) {
|
|
3664
|
+
selectedSystem.shift();
|
|
3665
|
+
systemTokens = sumTokens(tokensByIndex, selectedSystem);
|
|
3666
|
+
}
|
|
3667
|
+
if (systemTokens > maxTokens) {
|
|
3668
|
+
throw new ConfigError(
|
|
3669
|
+
"maxHistoryTokens is too small to fit the latest system message"
|
|
3670
|
+
);
|
|
3671
|
+
}
|
|
3672
|
+
const selected = new Set(selectedSystem);
|
|
3673
|
+
let remaining = maxTokens - systemTokens;
|
|
3674
|
+
for (let i = input.length - 1; i >= 0; i -= 1) {
|
|
3675
|
+
if (selected.has(i)) continue;
|
|
3676
|
+
const tokens = tokensByIndex[i];
|
|
3677
|
+
if (tokens <= remaining) {
|
|
3678
|
+
selected.add(i);
|
|
3679
|
+
remaining -= tokens;
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
const result = input.filter((_, idx) => selected.has(idx));
|
|
3683
|
+
if (result.length === 0) {
|
|
3684
|
+
throw new ConfigError("No messages fit within maxHistoryTokens");
|
|
3685
|
+
}
|
|
3686
|
+
return result;
|
|
3687
|
+
}
|
|
3688
|
+
function estimateTokens(text) {
|
|
3689
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
3690
|
+
}
|
|
3691
|
+
function isImagePart(part) {
|
|
3692
|
+
if (typeof part !== "object" || part === null) return false;
|
|
3693
|
+
const p = part;
|
|
3694
|
+
return p.type === "image" || p.type === "image_url";
|
|
3695
|
+
}
|
|
3696
|
+
function estimateImageTokens(part) {
|
|
3697
|
+
const detail = part.detail ?? "auto";
|
|
3698
|
+
if (detail === "low") return IMAGE_TOKENS_LOW_DETAIL;
|
|
3699
|
+
return IMAGE_TOKENS_HIGH_DETAIL;
|
|
3700
|
+
}
|
|
3701
|
+
function estimateTokensForMessage(message) {
|
|
3702
|
+
const segments = [message.role];
|
|
3703
|
+
let imageTokens = 0;
|
|
3704
|
+
for (const part of message.content || []) {
|
|
3705
|
+
if (part.type === "text" && part.text) {
|
|
3706
|
+
segments.push(part.text);
|
|
3707
|
+
} else if (isImagePart(part)) {
|
|
3708
|
+
imageTokens += estimateImageTokens(part);
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
if (message.toolCalls) {
|
|
3712
|
+
for (const call of message.toolCalls) {
|
|
3713
|
+
if (call.function?.name) segments.push(call.function.name);
|
|
3714
|
+
if (call.function?.arguments) segments.push(call.function.arguments);
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
if (message.toolCallId) {
|
|
3718
|
+
segments.push(message.toolCallId);
|
|
3719
|
+
}
|
|
3720
|
+
const textTokens = estimateTokens(segments.join("\n"));
|
|
3721
|
+
const toolOverhead = message.toolCalls ? message.toolCalls.length * TOOL_CALL_OVERHEAD_TOKENS : 0;
|
|
3722
|
+
return textTokens + MESSAGE_OVERHEAD_TOKENS + toolOverhead + imageTokens;
|
|
3723
|
+
}
|
|
3724
|
+
function normalizePositiveInt(value, label) {
|
|
3725
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
3726
|
+
throw new ConfigError(`${label} must be a positive number`);
|
|
3727
|
+
}
|
|
3728
|
+
return Math.floor(value);
|
|
3729
|
+
}
|
|
3730
|
+
function sumTokens(tokensByIndex, indices) {
|
|
3731
|
+
return indices.reduce((sum, idx) => sum + tokensByIndex[idx], 0);
|
|
3732
|
+
}
|
|
3733
|
+
async function resolveHistoryBudget(modelId, options, resolveModelContext) {
|
|
3734
|
+
const reservedOutputTokens = options.reserveOutputTokens === void 0 ? void 0 : normalizeNonNegativeInt(
|
|
3735
|
+
options.reserveOutputTokens,
|
|
3736
|
+
"reserveOutputTokens"
|
|
3737
|
+
);
|
|
3738
|
+
if (options.maxHistoryTokens !== void 0) {
|
|
3739
|
+
return {
|
|
3740
|
+
maxHistoryTokens: normalizePositiveInt(
|
|
3741
|
+
options.maxHistoryTokens,
|
|
3742
|
+
"maxHistoryTokens"
|
|
3743
|
+
),
|
|
3744
|
+
reservedOutputTokens
|
|
3745
|
+
};
|
|
3746
|
+
}
|
|
3747
|
+
if (!modelId) {
|
|
3748
|
+
throw new ConfigError(
|
|
3749
|
+
"model is required for context management when maxHistoryTokens is not set"
|
|
3750
|
+
);
|
|
3751
|
+
}
|
|
3752
|
+
const model = await resolveModelContext(modelId);
|
|
3753
|
+
if (!model) {
|
|
3754
|
+
throw new ConfigError(
|
|
3755
|
+
`Unknown model "${modelId}"; ensure the model exists in the ModelRelay catalog`
|
|
3756
|
+
);
|
|
3757
|
+
}
|
|
3758
|
+
const contextWindow = normalizePositiveInt(model.contextWindow, "context_window");
|
|
3759
|
+
const modelOutputTokens = model.maxOutputTokens === void 0 ? 0 : normalizeNonNegativeInt(model.maxOutputTokens, "max_output_tokens");
|
|
3760
|
+
const effectiveReserve = reservedOutputTokens ?? modelOutputTokens;
|
|
3761
|
+
const buffer = Math.max(
|
|
3762
|
+
DEFAULT_CONTEXT_BUFFER_TOKENS,
|
|
3763
|
+
Math.ceil(contextWindow * CONTEXT_BUFFER_RATIO)
|
|
3764
|
+
);
|
|
3765
|
+
const maxHistoryTokens = contextWindow - effectiveReserve - buffer;
|
|
3766
|
+
if (maxHistoryTokens <= 0) {
|
|
3767
|
+
throw new ConfigError(
|
|
3768
|
+
"model context window is too small after reserving output tokens; set maxHistoryTokens explicitly"
|
|
3769
|
+
);
|
|
3770
|
+
}
|
|
3771
|
+
return {
|
|
3772
|
+
maxHistoryTokens,
|
|
3773
|
+
reservedOutputTokens: effectiveReserve
|
|
3774
|
+
};
|
|
3775
|
+
}
|
|
3776
|
+
function normalizeNonNegativeInt(value, label) {
|
|
3777
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
3778
|
+
throw new ConfigError(`${label} must be a non-negative number`);
|
|
3779
|
+
}
|
|
3780
|
+
return Math.floor(value);
|
|
3781
|
+
}
|
|
3782
|
+
function getModelContextCacheEntry(client) {
|
|
3783
|
+
const existing = modelContextCache.get(client);
|
|
3784
|
+
if (existing) return existing;
|
|
3785
|
+
const entry = { byId: /* @__PURE__ */ new Map() };
|
|
3786
|
+
modelContextCache.set(client, entry);
|
|
3787
|
+
return entry;
|
|
3788
|
+
}
|
|
3789
|
+
async function populateModelContextCache(client, entry) {
|
|
3790
|
+
if (!entry.listPromise) {
|
|
3791
|
+
entry.listPromise = (async () => {
|
|
3792
|
+
const response = await client.http.json("/models");
|
|
3793
|
+
for (const model of response.models) {
|
|
3794
|
+
entry.byId.set(model.model_id, {
|
|
3795
|
+
contextWindow: model.context_window,
|
|
3796
|
+
maxOutputTokens: model.max_output_tokens ?? void 0
|
|
3797
|
+
});
|
|
3798
|
+
}
|
|
3799
|
+
})();
|
|
3800
|
+
}
|
|
3801
|
+
await entry.listPromise;
|
|
3802
|
+
}
|
|
3803
|
+
|
|
3804
|
+
// src/sessions/remote_session.ts
|
|
3805
|
+
var RemoteSession = class _RemoteSession {
|
|
3806
|
+
constructor(client, http, sessionData, options = {}) {
|
|
3807
|
+
this.type = "remote";
|
|
3808
|
+
this.messages = [];
|
|
3809
|
+
this.artifacts = /* @__PURE__ */ new Map();
|
|
3810
|
+
this.nextSeq = 1;
|
|
3811
|
+
this.pendingMessages = [];
|
|
3812
|
+
this.currentEvents = [];
|
|
3813
|
+
this.currentUsage = {
|
|
3814
|
+
inputTokens: 0,
|
|
3815
|
+
outputTokens: 0,
|
|
3816
|
+
totalTokens: 0,
|
|
3817
|
+
llmCalls: 0,
|
|
3818
|
+
toolCalls: 0
|
|
3819
|
+
};
|
|
3820
|
+
this.client = client;
|
|
3821
|
+
this.http = http;
|
|
3822
|
+
this.id = asSessionId(sessionData.id);
|
|
3823
|
+
this.metadata = sessionData.metadata;
|
|
3824
|
+
this.customerId = sessionData.customer_id || options.customerId;
|
|
3825
|
+
this.createdAt = new Date(sessionData.created_at);
|
|
3826
|
+
this.updatedAt = new Date(sessionData.updated_at);
|
|
3827
|
+
this.toolRegistry = options.toolRegistry;
|
|
3828
|
+
this.defaultModel = options.defaultModel;
|
|
3829
|
+
this.defaultProvider = options.defaultProvider;
|
|
3830
|
+
this.defaultTools = options.defaultTools;
|
|
3831
|
+
this.resolveModelContext = createModelContextResolver(client);
|
|
3832
|
+
if ("messages" in sessionData && sessionData.messages) {
|
|
3833
|
+
this.messages = sessionData.messages.map((m) => ({
|
|
3834
|
+
type: "message",
|
|
3835
|
+
role: m.role,
|
|
3836
|
+
content: m.content,
|
|
3837
|
+
seq: m.seq,
|
|
3838
|
+
createdAt: new Date(m.created_at),
|
|
3839
|
+
runId: m.run_id ? parseRunId(m.run_id) : void 0
|
|
3840
|
+
}));
|
|
3841
|
+
this.nextSeq = this.messages.length + 1;
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
/**
|
|
3845
|
+
* Create a new remote session on the server.
|
|
3846
|
+
*
|
|
3847
|
+
* @param client - ModelRelay client
|
|
3848
|
+
* @param options - Session configuration
|
|
3849
|
+
* @returns A new RemoteSession instance
|
|
3850
|
+
*/
|
|
3851
|
+
static async create(client, options = {}) {
|
|
3852
|
+
const http = client.http;
|
|
3853
|
+
const response = await http.request("/sessions", {
|
|
3854
|
+
method: "POST",
|
|
3855
|
+
body: {
|
|
3856
|
+
customer_id: options.customerId,
|
|
3857
|
+
metadata: options.metadata || {}
|
|
3858
|
+
}
|
|
3859
|
+
});
|
|
3860
|
+
const data = await response.json();
|
|
3861
|
+
return new _RemoteSession(client, http, data, options);
|
|
3862
|
+
}
|
|
3863
|
+
/**
|
|
3864
|
+
* Get an existing remote session by ID.
|
|
3865
|
+
*
|
|
3866
|
+
* @param client - ModelRelay client
|
|
3867
|
+
* @param sessionId - ID of the session to retrieve
|
|
3868
|
+
* @param options - Optional configuration (toolRegistry, defaults)
|
|
3869
|
+
* @returns The RemoteSession instance
|
|
3870
|
+
*/
|
|
3871
|
+
static async get(client, sessionId, options = {}) {
|
|
3872
|
+
const http = client.http;
|
|
3873
|
+
const id = typeof sessionId === "string" ? sessionId : String(sessionId);
|
|
3874
|
+
const response = await http.request(`/sessions/${id}`, {
|
|
3875
|
+
method: "GET"
|
|
3876
|
+
});
|
|
3877
|
+
const data = await response.json();
|
|
3878
|
+
return new _RemoteSession(client, http, data, options);
|
|
3879
|
+
}
|
|
3880
|
+
/**
|
|
3881
|
+
* List remote sessions.
|
|
3882
|
+
*
|
|
3883
|
+
* @param client - ModelRelay client
|
|
3884
|
+
* @param options - List options
|
|
3885
|
+
* @returns Paginated list of session info
|
|
3886
|
+
*/
|
|
3887
|
+
static async list(client, options = {}) {
|
|
3888
|
+
const http = client.http;
|
|
3313
3889
|
const params = new URLSearchParams();
|
|
3314
3890
|
if (options.limit) params.set("limit", String(options.limit));
|
|
3315
3891
|
if (options.offset) params.set("offset", String(options.offset));
|
|
@@ -3337,7 +3913,7 @@ var RemoteSession = class _RemoteSession {
|
|
|
3337
3913
|
* @param sessionId - ID of the session to delete
|
|
3338
3914
|
*/
|
|
3339
3915
|
static async delete(client, sessionId) {
|
|
3340
|
-
const http =
|
|
3916
|
+
const http = client.http;
|
|
3341
3917
|
const id = typeof sessionId === "string" ? sessionId : String(sessionId);
|
|
3342
3918
|
await http.request(`/sessions/${id}`, {
|
|
3343
3919
|
method: "DELETE"
|
|
@@ -3364,21 +3940,22 @@ var RemoteSession = class _RemoteSession {
|
|
|
3364
3940
|
this.resetRunState();
|
|
3365
3941
|
try {
|
|
3366
3942
|
const input = await this.buildInput(options);
|
|
3367
|
-
const tools =
|
|
3943
|
+
const tools = mergeTools(this.defaultTools, options.tools);
|
|
3944
|
+
const mainNodeId = parseNodeId("main");
|
|
3368
3945
|
const spec = {
|
|
3369
3946
|
kind: "workflow",
|
|
3370
3947
|
name: `session-${this.id}-turn-${this.nextSeq}`,
|
|
3371
3948
|
model: options.model || this.defaultModel,
|
|
3372
3949
|
nodes: [
|
|
3373
3950
|
{
|
|
3374
|
-
id:
|
|
3951
|
+
id: mainNodeId,
|
|
3375
3952
|
type: "llm",
|
|
3376
3953
|
input,
|
|
3377
3954
|
tools,
|
|
3378
3955
|
tool_execution: this.toolRegistry ? { mode: "client" } : void 0
|
|
3379
3956
|
}
|
|
3380
3957
|
],
|
|
3381
|
-
outputs: [{ name: "result", from:
|
|
3958
|
+
outputs: [{ name: parseOutputName("result"), from: mainNodeId }]
|
|
3382
3959
|
};
|
|
3383
3960
|
const run = await this.client.runs.create(spec, {
|
|
3384
3961
|
customerId: options.customerId || this.customerId,
|
|
@@ -3391,7 +3968,8 @@ var RemoteSession = class _RemoteSession {
|
|
|
3391
3968
|
return {
|
|
3392
3969
|
status: "error",
|
|
3393
3970
|
error: error.message,
|
|
3394
|
-
|
|
3971
|
+
cause: error,
|
|
3972
|
+
runId: this.currentRunId,
|
|
3395
3973
|
usage: { ...this.currentUsage },
|
|
3396
3974
|
events: [...this.currentEvents]
|
|
3397
3975
|
};
|
|
@@ -3470,10 +4048,25 @@ var RemoteSession = class _RemoteSession {
|
|
|
3470
4048
|
return message;
|
|
3471
4049
|
}
|
|
3472
4050
|
async buildInput(options) {
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
4051
|
+
const baseInput = messagesToInput(this.messages);
|
|
4052
|
+
if (!options.contextManagement || options.contextManagement === "none") {
|
|
4053
|
+
return baseInput;
|
|
4054
|
+
}
|
|
4055
|
+
const modelId = options.model ?? this.defaultModel;
|
|
4056
|
+
if (!modelId) {
|
|
4057
|
+
throw new ConfigError(
|
|
4058
|
+
"model is required for context management; set options.model or a session defaultModel"
|
|
4059
|
+
);
|
|
4060
|
+
}
|
|
4061
|
+
return prepareInputWithContext(
|
|
4062
|
+
baseInput,
|
|
4063
|
+
{
|
|
4064
|
+
model: modelId,
|
|
4065
|
+
strategy: options.contextManagement,
|
|
4066
|
+
maxHistoryTokens: options.maxHistoryTokens,
|
|
4067
|
+
reserveOutputTokens: options.reserveOutputTokens,
|
|
4068
|
+
onTruncate: options.onContextTruncate
|
|
4069
|
+
},
|
|
3477
4070
|
this.resolveModelContext
|
|
3478
4071
|
);
|
|
3479
4072
|
}
|
|
@@ -3482,13 +4075,7 @@ var RemoteSession = class _RemoteSession {
|
|
|
3482
4075
|
this.currentNodeId = void 0;
|
|
3483
4076
|
this.currentWaiting = void 0;
|
|
3484
4077
|
this.currentEvents = [];
|
|
3485
|
-
this.currentUsage =
|
|
3486
|
-
inputTokens: 0,
|
|
3487
|
-
outputTokens: 0,
|
|
3488
|
-
totalTokens: 0,
|
|
3489
|
-
llmCalls: 0,
|
|
3490
|
-
toolCalls: 0
|
|
3491
|
-
};
|
|
4078
|
+
this.currentUsage = emptyUsage();
|
|
3492
4079
|
}
|
|
3493
4080
|
async processRunEvents(signal) {
|
|
3494
4081
|
if (!this.currentRunId) {
|
|
@@ -3727,26 +4314,6 @@ var RemoteSession = class _RemoteSession {
|
|
|
3727
4314
|
return text.trim() ? text : void 0;
|
|
3728
4315
|
}
|
|
3729
4316
|
};
|
|
3730
|
-
function getHTTPClient(client) {
|
|
3731
|
-
return client.http;
|
|
3732
|
-
}
|
|
3733
|
-
function mergeTools2(defaults, overrides) {
|
|
3734
|
-
if (!defaults && !overrides) return void 0;
|
|
3735
|
-
if (!defaults) return overrides;
|
|
3736
|
-
if (!overrides) return defaults;
|
|
3737
|
-
const merged = /* @__PURE__ */ new Map();
|
|
3738
|
-
for (const tool of defaults) {
|
|
3739
|
-
if (tool.type === "function" && tool.function) {
|
|
3740
|
-
merged.set(tool.function.name, tool);
|
|
3741
|
-
}
|
|
3742
|
-
}
|
|
3743
|
-
for (const tool of overrides) {
|
|
3744
|
-
if (tool.type === "function" && tool.function) {
|
|
3745
|
-
merged.set(tool.function.name, tool);
|
|
3746
|
-
}
|
|
3747
|
-
}
|
|
3748
|
-
return Array.from(merged.values());
|
|
3749
|
-
}
|
|
3750
4317
|
|
|
3751
4318
|
// src/sessions/client.ts
|
|
3752
4319
|
var SessionsClient = class {
|
|
@@ -3863,111 +4430,1487 @@ var SessionsClient = class {
|
|
|
3863
4430
|
customerId: options.customerId
|
|
3864
4431
|
});
|
|
3865
4432
|
}
|
|
3866
|
-
/**
|
|
3867
|
-
* Delete a remote session.
|
|
3868
|
-
*
|
|
3869
|
-
* Requires a secret key.
|
|
3870
|
-
*
|
|
3871
|
-
* @param sessionId - ID of the session to delete
|
|
3872
|
-
*
|
|
3873
|
-
* @example
|
|
3874
|
-
* ```typescript
|
|
3875
|
-
* await client.sessions.delete("session-id");
|
|
3876
|
-
* ```
|
|
3877
|
-
*/
|
|
3878
|
-
async delete(sessionId) {
|
|
3879
|
-
return RemoteSession.delete(this.modelRelay, sessionId);
|
|
4433
|
+
/**
|
|
4434
|
+
* Delete a remote session.
|
|
4435
|
+
*
|
|
4436
|
+
* Requires a secret key.
|
|
4437
|
+
*
|
|
4438
|
+
* @param sessionId - ID of the session to delete
|
|
4439
|
+
*
|
|
4440
|
+
* @example
|
|
4441
|
+
* ```typescript
|
|
4442
|
+
* await client.sessions.delete("session-id");
|
|
4443
|
+
* ```
|
|
4444
|
+
*/
|
|
4445
|
+
async delete(sessionId) {
|
|
4446
|
+
return RemoteSession.delete(this.modelRelay, sessionId);
|
|
4447
|
+
}
|
|
4448
|
+
};
|
|
4449
|
+
|
|
4450
|
+
// src/tiers.ts
|
|
4451
|
+
function defaultTierModelId(tier) {
|
|
4452
|
+
const def = tier.models.find((m) => m.is_default);
|
|
4453
|
+
if (def) return def.model_id;
|
|
4454
|
+
if (tier.models.length === 1) return tier.models[0].model_id;
|
|
4455
|
+
return void 0;
|
|
4456
|
+
}
|
|
4457
|
+
var TiersClient = class {
|
|
4458
|
+
constructor(http, cfg) {
|
|
4459
|
+
this.http = http;
|
|
4460
|
+
this.apiKey = cfg.apiKey ? parseApiKey(cfg.apiKey) : void 0;
|
|
4461
|
+
this.hasSecretKey = this.apiKey ? isSecretKey(this.apiKey) : false;
|
|
4462
|
+
this.hasAccessToken = !!cfg.accessToken?.trim();
|
|
4463
|
+
}
|
|
4464
|
+
ensureAuth() {
|
|
4465
|
+
if (!this.apiKey && !this.hasAccessToken) {
|
|
4466
|
+
throw new ConfigError(
|
|
4467
|
+
"API key (mr_sk_*) or bearer token required for tier operations"
|
|
4468
|
+
);
|
|
4469
|
+
}
|
|
4470
|
+
}
|
|
4471
|
+
ensureSecretKey() {
|
|
4472
|
+
if (!this.apiKey || !this.hasSecretKey) {
|
|
4473
|
+
throw new ConfigError(
|
|
4474
|
+
"Secret key (mr_sk_*) required for checkout operations"
|
|
4475
|
+
);
|
|
4476
|
+
}
|
|
4477
|
+
}
|
|
4478
|
+
/**
|
|
4479
|
+
* List all tiers in the project.
|
|
4480
|
+
*/
|
|
4481
|
+
async list() {
|
|
4482
|
+
this.ensureAuth();
|
|
4483
|
+
const response = await this.http.json("/tiers", {
|
|
4484
|
+
method: "GET",
|
|
4485
|
+
apiKey: this.apiKey
|
|
4486
|
+
});
|
|
4487
|
+
return response.tiers;
|
|
4488
|
+
}
|
|
4489
|
+
/**
|
|
4490
|
+
* Get a tier by ID.
|
|
4491
|
+
*/
|
|
4492
|
+
async get(tierId) {
|
|
4493
|
+
this.ensureAuth();
|
|
4494
|
+
if (!tierId?.trim()) {
|
|
4495
|
+
throw new ConfigError("tierId is required");
|
|
4496
|
+
}
|
|
4497
|
+
const response = await this.http.json(`/tiers/${tierId}`, {
|
|
4498
|
+
method: "GET",
|
|
4499
|
+
apiKey: this.apiKey
|
|
4500
|
+
});
|
|
4501
|
+
return response.tier;
|
|
4502
|
+
}
|
|
4503
|
+
/**
|
|
4504
|
+
* Create a Stripe checkout session for a tier (Stripe-first flow).
|
|
4505
|
+
*
|
|
4506
|
+
* This enables users to subscribe before authenticating. Stripe collects
|
|
4507
|
+
* the customer's email during checkout. After checkout completes, a
|
|
4508
|
+
* customer record is created with the email from Stripe. Your backend
|
|
4509
|
+
* can map it to your app user and mint customer tokens as needed.
|
|
4510
|
+
*
|
|
4511
|
+
* Requires a secret key (mr_sk_*).
|
|
4512
|
+
*
|
|
4513
|
+
* @param tierId - The tier ID to create a checkout session for
|
|
4514
|
+
* @param request - Checkout session request with redirect URLs
|
|
4515
|
+
* @returns Checkout session with Stripe URL
|
|
4516
|
+
*/
|
|
4517
|
+
async checkout(tierId, request) {
|
|
4518
|
+
this.ensureSecretKey();
|
|
4519
|
+
if (!tierId?.trim()) {
|
|
4520
|
+
throw new ConfigError("tierId is required");
|
|
4521
|
+
}
|
|
4522
|
+
if (!request.success_url?.trim()) {
|
|
4523
|
+
throw new ConfigError("success_url is required");
|
|
4524
|
+
}
|
|
4525
|
+
if (!request.cancel_url?.trim()) {
|
|
4526
|
+
throw new ConfigError("cancel_url is required");
|
|
4527
|
+
}
|
|
4528
|
+
return await this.http.json(
|
|
4529
|
+
`/tiers/${tierId}/checkout`,
|
|
4530
|
+
{
|
|
4531
|
+
method: "POST",
|
|
4532
|
+
apiKey: this.apiKey,
|
|
4533
|
+
body: request
|
|
4534
|
+
}
|
|
4535
|
+
);
|
|
4536
|
+
}
|
|
4537
|
+
};
|
|
4538
|
+
|
|
4539
|
+
// src/plugins.ts
|
|
4540
|
+
import { z as z2 } from "zod";
|
|
4541
|
+
|
|
4542
|
+
// src/tools_user_ask.ts
|
|
4543
|
+
var USER_ASK_TOOL_NAME = "user.ask";
|
|
4544
|
+
var userAskSchema = {
|
|
4545
|
+
type: "object",
|
|
4546
|
+
properties: {
|
|
4547
|
+
question: {
|
|
4548
|
+
type: "string",
|
|
4549
|
+
minLength: 1,
|
|
4550
|
+
description: "The question to ask the user."
|
|
4551
|
+
},
|
|
4552
|
+
options: {
|
|
4553
|
+
type: "array",
|
|
4554
|
+
items: {
|
|
4555
|
+
type: "object",
|
|
4556
|
+
properties: {
|
|
4557
|
+
label: { type: "string", minLength: 1 },
|
|
4558
|
+
description: { type: "string" }
|
|
4559
|
+
},
|
|
4560
|
+
required: ["label"]
|
|
4561
|
+
},
|
|
4562
|
+
description: "Optional multiple choice options."
|
|
4563
|
+
},
|
|
4564
|
+
allow_freeform: {
|
|
4565
|
+
type: "boolean",
|
|
4566
|
+
default: true,
|
|
4567
|
+
description: "Allow user to type a custom response."
|
|
4568
|
+
}
|
|
4569
|
+
},
|
|
4570
|
+
required: ["question"]
|
|
4571
|
+
};
|
|
4572
|
+
function createUserAskTool() {
|
|
4573
|
+
return createFunctionTool(
|
|
4574
|
+
USER_ASK_TOOL_NAME,
|
|
4575
|
+
"Ask the user a clarifying question.",
|
|
4576
|
+
userAskSchema
|
|
4577
|
+
);
|
|
4578
|
+
}
|
|
4579
|
+
function isUserAskToolCall(call) {
|
|
4580
|
+
return call.type === "function" && call.function?.name === USER_ASK_TOOL_NAME;
|
|
4581
|
+
}
|
|
4582
|
+
function parseUserAskArgs(call) {
|
|
4583
|
+
const raw = getToolArgsRaw(call);
|
|
4584
|
+
if (!raw) {
|
|
4585
|
+
throw new ToolArgumentError({
|
|
4586
|
+
message: "user.ask arguments required",
|
|
4587
|
+
toolCallId: call.id,
|
|
4588
|
+
toolName: USER_ASK_TOOL_NAME,
|
|
4589
|
+
rawArguments: raw
|
|
4590
|
+
});
|
|
4591
|
+
}
|
|
4592
|
+
let parsed;
|
|
4593
|
+
try {
|
|
4594
|
+
parsed = JSON.parse(raw);
|
|
4595
|
+
} catch (err) {
|
|
4596
|
+
throw new ToolArgumentError({
|
|
4597
|
+
message: "user.ask arguments must be valid JSON",
|
|
4598
|
+
toolCallId: call.id,
|
|
4599
|
+
toolName: USER_ASK_TOOL_NAME,
|
|
4600
|
+
rawArguments: raw,
|
|
4601
|
+
cause: err
|
|
4602
|
+
});
|
|
4603
|
+
}
|
|
4604
|
+
const question = parsed.question?.trim?.() ?? "";
|
|
4605
|
+
if (!question) {
|
|
4606
|
+
throw new ToolArgumentError({
|
|
4607
|
+
message: "user.ask question required",
|
|
4608
|
+
toolCallId: call.id,
|
|
4609
|
+
toolName: USER_ASK_TOOL_NAME,
|
|
4610
|
+
rawArguments: raw
|
|
4611
|
+
});
|
|
4612
|
+
}
|
|
4613
|
+
if (parsed.options?.length) {
|
|
4614
|
+
for (const opt of parsed.options) {
|
|
4615
|
+
if (!opt?.label?.trim?.()) {
|
|
4616
|
+
throw new ToolArgumentError({
|
|
4617
|
+
message: "user.ask options require label",
|
|
4618
|
+
toolCallId: call.id,
|
|
4619
|
+
toolName: USER_ASK_TOOL_NAME,
|
|
4620
|
+
rawArguments: raw
|
|
4621
|
+
});
|
|
4622
|
+
}
|
|
4623
|
+
}
|
|
4624
|
+
}
|
|
4625
|
+
return {
|
|
4626
|
+
question,
|
|
4627
|
+
options: parsed.options,
|
|
4628
|
+
allow_freeform: parsed.allow_freeform
|
|
4629
|
+
};
|
|
4630
|
+
}
|
|
4631
|
+
function serializeUserAskResult(result) {
|
|
4632
|
+
const answer = result.answer?.trim?.() ?? "";
|
|
4633
|
+
if (!answer) {
|
|
4634
|
+
throw new ToolArgumentError({
|
|
4635
|
+
message: "user.ask answer required",
|
|
4636
|
+
toolCallId: "",
|
|
4637
|
+
toolName: USER_ASK_TOOL_NAME,
|
|
4638
|
+
rawArguments: ""
|
|
4639
|
+
});
|
|
4640
|
+
}
|
|
4641
|
+
return JSON.stringify({ answer, is_freeform: result.is_freeform });
|
|
4642
|
+
}
|
|
4643
|
+
function userAskResultFreeform(answer) {
|
|
4644
|
+
return serializeUserAskResult({ answer, is_freeform: true });
|
|
4645
|
+
}
|
|
4646
|
+
function userAskResultChoice(answer) {
|
|
4647
|
+
return serializeUserAskResult({ answer, is_freeform: false });
|
|
4648
|
+
}
|
|
4649
|
+
|
|
4650
|
+
// src/tools_runner.ts
|
|
4651
|
+
var ToolRunner = class {
|
|
4652
|
+
constructor(options) {
|
|
4653
|
+
this.registry = options.registry;
|
|
4654
|
+
this.runsClient = options.runsClient;
|
|
4655
|
+
this.customerId = options.customerId;
|
|
4656
|
+
this.onBeforeExecute = options.onBeforeExecute;
|
|
4657
|
+
this.onAfterExecute = options.onAfterExecute;
|
|
4658
|
+
this.onSubmitted = options.onSubmitted;
|
|
4659
|
+
this.onUserAsk = options.onUserAsk;
|
|
4660
|
+
this.onError = options.onError;
|
|
4661
|
+
}
|
|
4662
|
+
/**
|
|
4663
|
+
* Handles a node_waiting event by executing tools and submitting results.
|
|
4664
|
+
*
|
|
4665
|
+
* @param runId - The run ID
|
|
4666
|
+
* @param nodeId - The node ID that is waiting
|
|
4667
|
+
* @param waiting - The waiting state with pending tool calls
|
|
4668
|
+
* @returns The submission response with accepted count and new status
|
|
4669
|
+
*
|
|
4670
|
+
* @example
|
|
4671
|
+
* ```typescript
|
|
4672
|
+
* for await (const event of client.runs.events(runId)) {
|
|
4673
|
+
* if (event.type === "node_waiting") {
|
|
4674
|
+
* const result = await runner.handleNodeWaiting(
|
|
4675
|
+
* runId,
|
|
4676
|
+
* event.node_id,
|
|
4677
|
+
* event.waiting
|
|
4678
|
+
* );
|
|
4679
|
+
* console.log(`Submitted ${result.accepted} results, status: ${result.status}`);
|
|
4680
|
+
* }
|
|
4681
|
+
* }
|
|
4682
|
+
* ```
|
|
4683
|
+
*/
|
|
4684
|
+
async handleNodeWaiting(runId, nodeId, waiting) {
|
|
4685
|
+
const results = [];
|
|
4686
|
+
for (const pending of waiting.pending_tool_calls) {
|
|
4687
|
+
try {
|
|
4688
|
+
await this.onBeforeExecute?.(pending);
|
|
4689
|
+
const toolCall = createToolCall(
|
|
4690
|
+
pending.tool_call.id,
|
|
4691
|
+
pending.tool_call.name,
|
|
4692
|
+
pending.tool_call.arguments
|
|
4693
|
+
);
|
|
4694
|
+
let result;
|
|
4695
|
+
if (pending.tool_call.name === USER_ASK_TOOL_NAME) {
|
|
4696
|
+
if (!this.onUserAsk) {
|
|
4697
|
+
throw new Error("user.ask requires onUserAsk handler");
|
|
4698
|
+
}
|
|
4699
|
+
const args = parseUserAskArgs(toolCall);
|
|
4700
|
+
const response2 = await this.onUserAsk(pending, args);
|
|
4701
|
+
const output = typeof response2 === "string" ? serializeUserAskResult({ answer: response2, is_freeform: true }) : serializeUserAskResult(response2);
|
|
4702
|
+
result = {
|
|
4703
|
+
toolCallId: pending.tool_call.id,
|
|
4704
|
+
toolName: pending.tool_call.name,
|
|
4705
|
+
result: output,
|
|
4706
|
+
isRetryable: false
|
|
4707
|
+
};
|
|
4708
|
+
} else {
|
|
4709
|
+
result = await this.registry.execute(toolCall);
|
|
4710
|
+
}
|
|
4711
|
+
results.push(result);
|
|
4712
|
+
await this.onAfterExecute?.(result);
|
|
4713
|
+
} catch (err) {
|
|
4714
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
4715
|
+
await this.onError?.(error, pending);
|
|
4716
|
+
results.push({
|
|
4717
|
+
toolCallId: pending.tool_call.id,
|
|
4718
|
+
toolName: pending.tool_call.name,
|
|
4719
|
+
result: null,
|
|
4720
|
+
error: error.message
|
|
4721
|
+
});
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
const response = await this.runsClient.submitToolResults(
|
|
4725
|
+
runId,
|
|
4726
|
+
{
|
|
4727
|
+
node_id: nodeId,
|
|
4728
|
+
step: waiting.step,
|
|
4729
|
+
request_id: waiting.request_id,
|
|
4730
|
+
results: results.map((r) => ({
|
|
4731
|
+
tool_call: {
|
|
4732
|
+
id: r.toolCallId,
|
|
4733
|
+
name: r.toolName
|
|
4734
|
+
},
|
|
4735
|
+
output: r.error ? `Error: ${r.error}` : typeof r.result === "string" ? r.result : JSON.stringify(r.result)
|
|
4736
|
+
}))
|
|
4737
|
+
},
|
|
4738
|
+
{ customerId: this.customerId }
|
|
4739
|
+
);
|
|
4740
|
+
await this.onSubmitted?.(runId, response.accepted, response.status);
|
|
4741
|
+
return {
|
|
4742
|
+
accepted: response.accepted,
|
|
4743
|
+
status: response.status,
|
|
4744
|
+
results
|
|
4745
|
+
};
|
|
4746
|
+
}
|
|
4747
|
+
/**
|
|
4748
|
+
* Processes a stream of run events, automatically handling node_waiting events.
|
|
4749
|
+
*
|
|
4750
|
+
* This is the main entry point for running a workflow with client-side tools.
|
|
4751
|
+
* It yields all events through (including node_waiting after handling).
|
|
4752
|
+
*
|
|
4753
|
+
* @param runId - The run ID to process
|
|
4754
|
+
* @param events - AsyncIterable of run events (from RunsClient.events())
|
|
4755
|
+
* @yields All run events, with node_waiting events handled automatically
|
|
4756
|
+
*
|
|
4757
|
+
* @example
|
|
4758
|
+
* ```typescript
|
|
4759
|
+
* const run = await client.runs.create(workflowSpec);
|
|
4760
|
+
* const eventStream = client.runs.events(run.run_id);
|
|
4761
|
+
*
|
|
4762
|
+
* for await (const event of runner.processEvents(run.run_id, eventStream)) {
|
|
4763
|
+
* switch (event.type) {
|
|
4764
|
+
* case "node_started":
|
|
4765
|
+
* console.log(`Node ${event.node_id} started`);
|
|
4766
|
+
* break;
|
|
4767
|
+
* case "node_succeeded":
|
|
4768
|
+
* console.log(`Node ${event.node_id} succeeded`);
|
|
4769
|
+
* break;
|
|
4770
|
+
* case "run_succeeded":
|
|
4771
|
+
* console.log("Run completed!");
|
|
4772
|
+
* break;
|
|
4773
|
+
* }
|
|
4774
|
+
* }
|
|
4775
|
+
* ```
|
|
4776
|
+
*/
|
|
4777
|
+
async *processEvents(runId, events) {
|
|
4778
|
+
for await (const event of events) {
|
|
4779
|
+
if (event.type === "node_waiting") {
|
|
4780
|
+
const waitingEvent = event;
|
|
4781
|
+
try {
|
|
4782
|
+
await this.handleNodeWaiting(
|
|
4783
|
+
runId,
|
|
4784
|
+
waitingEvent.node_id,
|
|
4785
|
+
waitingEvent.waiting
|
|
4786
|
+
);
|
|
4787
|
+
} catch (err) {
|
|
4788
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
4789
|
+
await this.onError?.(error);
|
|
4790
|
+
throw error;
|
|
4791
|
+
}
|
|
4792
|
+
}
|
|
4793
|
+
yield event;
|
|
4794
|
+
}
|
|
4795
|
+
}
|
|
4796
|
+
/**
|
|
4797
|
+
* Checks if a run event is a node_waiting event.
|
|
4798
|
+
* Utility for filtering events when not using processEvents().
|
|
4799
|
+
*/
|
|
4800
|
+
static isNodeWaiting(event) {
|
|
4801
|
+
return event.type === "node_waiting";
|
|
4802
|
+
}
|
|
4803
|
+
/**
|
|
4804
|
+
* Checks if a run status is terminal (succeeded, failed, or canceled).
|
|
4805
|
+
* Utility for determining when to stop polling.
|
|
4806
|
+
*/
|
|
4807
|
+
static isTerminalStatus(status) {
|
|
4808
|
+
return status === "succeeded" || status === "failed" || status === "canceled";
|
|
4809
|
+
}
|
|
4810
|
+
};
|
|
4811
|
+
function createToolRunner(options) {
|
|
4812
|
+
return new ToolRunner(options);
|
|
4813
|
+
}
|
|
4814
|
+
|
|
4815
|
+
// src/plugins.ts
|
|
4816
|
+
var PluginToolNames = {
|
|
4817
|
+
FS_READ_FILE: "fs.read_file",
|
|
4818
|
+
FS_LIST_FILES: "fs.list_files",
|
|
4819
|
+
FS_SEARCH: "fs.search",
|
|
4820
|
+
FS_EDIT: "fs.edit",
|
|
4821
|
+
BASH: "bash",
|
|
4822
|
+
WRITE_FILE: "write_file",
|
|
4823
|
+
USER_ASK: "user.ask"
|
|
4824
|
+
};
|
|
4825
|
+
var OrchestrationModes = {
|
|
4826
|
+
DAG: "dag",
|
|
4827
|
+
Dynamic: "dynamic"
|
|
4828
|
+
};
|
|
4829
|
+
var PluginOrchestrationErrorCodes = {
|
|
4830
|
+
InvalidPlan: "INVALID_PLAN",
|
|
4831
|
+
UnknownAgent: "UNKNOWN_AGENT",
|
|
4832
|
+
MissingDescription: "MISSING_DESCRIPTION",
|
|
4833
|
+
UnknownTool: "UNKNOWN_TOOL",
|
|
4834
|
+
InvalidDependency: "INVALID_DEPENDENCY",
|
|
4835
|
+
InvalidToolConfig: "INVALID_TOOL_CONFIG"
|
|
4836
|
+
};
|
|
4837
|
+
var PluginOrchestrationError = class extends Error {
|
|
4838
|
+
constructor(code, message) {
|
|
4839
|
+
super(`plugin orchestration: ${message}`);
|
|
4840
|
+
this.code = code;
|
|
4841
|
+
}
|
|
4842
|
+
};
|
|
4843
|
+
var DEFAULT_PLUGIN_REF = "HEAD";
|
|
4844
|
+
var DEFAULT_CACHE_TTL_MS = 5 * 6e4;
|
|
4845
|
+
var DEFAULT_GITHUB_API_BASE = "https://api.github.com";
|
|
4846
|
+
var DEFAULT_GITHUB_RAW_BASE = "https://raw.githubusercontent.com";
|
|
4847
|
+
var defaultDynamicToolNames = [
|
|
4848
|
+
PluginToolNames.FS_READ_FILE,
|
|
4849
|
+
PluginToolNames.FS_LIST_FILES,
|
|
4850
|
+
PluginToolNames.FS_SEARCH
|
|
4851
|
+
];
|
|
4852
|
+
var allowedToolSet = new Set(Object.values(PluginToolNames));
|
|
4853
|
+
var workflowIntentSchema = z2.object({
|
|
4854
|
+
kind: z2.literal(WorkflowKinds.WorkflowIntent),
|
|
4855
|
+
name: z2.string().optional(),
|
|
4856
|
+
model: z2.string().optional(),
|
|
4857
|
+
max_parallelism: z2.number().int().positive().optional(),
|
|
4858
|
+
inputs: z2.array(
|
|
4859
|
+
z2.object({
|
|
4860
|
+
name: z2.string().min(1),
|
|
4861
|
+
type: z2.string().optional(),
|
|
4862
|
+
required: z2.boolean().optional(),
|
|
4863
|
+
description: z2.string().optional(),
|
|
4864
|
+
default: z2.unknown().optional()
|
|
4865
|
+
})
|
|
4866
|
+
).optional(),
|
|
4867
|
+
nodes: z2.array(
|
|
4868
|
+
z2.object({
|
|
4869
|
+
id: z2.string().min(1),
|
|
4870
|
+
type: z2.enum([
|
|
4871
|
+
WorkflowNodeTypesIntent.LLM,
|
|
4872
|
+
WorkflowNodeTypesIntent.JoinAll,
|
|
4873
|
+
WorkflowNodeTypesIntent.JoinAny,
|
|
4874
|
+
WorkflowNodeTypesIntent.JoinCollect,
|
|
4875
|
+
WorkflowNodeTypesIntent.TransformJSON,
|
|
4876
|
+
WorkflowNodeTypesIntent.MapFanout
|
|
4877
|
+
]),
|
|
4878
|
+
depends_on: z2.array(z2.string().min(1)).optional(),
|
|
4879
|
+
model: z2.string().optional(),
|
|
4880
|
+
system: z2.string().optional(),
|
|
4881
|
+
user: z2.string().optional(),
|
|
4882
|
+
input: z2.array(z2.unknown()).optional(),
|
|
4883
|
+
stream: z2.boolean().optional(),
|
|
4884
|
+
tools: z2.array(
|
|
4885
|
+
z2.union([
|
|
4886
|
+
z2.string(),
|
|
4887
|
+
z2.object({}).passthrough()
|
|
4888
|
+
])
|
|
4889
|
+
).optional(),
|
|
4890
|
+
tool_execution: z2.object({ mode: z2.enum(["server", "client", "agentic"]) }).optional(),
|
|
4891
|
+
limit: z2.number().int().positive().optional(),
|
|
4892
|
+
timeout_ms: z2.number().int().positive().optional(),
|
|
4893
|
+
predicate: z2.object({}).passthrough().optional(),
|
|
4894
|
+
items_from: z2.string().optional(),
|
|
4895
|
+
items_from_input: z2.string().optional(),
|
|
4896
|
+
items_pointer: z2.string().optional(),
|
|
4897
|
+
items_path: z2.string().optional(),
|
|
4898
|
+
subnode: z2.object({}).passthrough().optional(),
|
|
4899
|
+
max_parallelism: z2.number().int().positive().optional(),
|
|
4900
|
+
object: z2.record(z2.unknown()).optional(),
|
|
4901
|
+
merge: z2.array(z2.unknown()).optional()
|
|
4902
|
+
}).passthrough()
|
|
4903
|
+
).min(1),
|
|
4904
|
+
outputs: z2.array(
|
|
4905
|
+
z2.object({
|
|
4906
|
+
name: z2.string().min(1),
|
|
4907
|
+
from: z2.string().min(1),
|
|
4908
|
+
pointer: z2.string().optional()
|
|
4909
|
+
})
|
|
4910
|
+
).min(1)
|
|
4911
|
+
}).passthrough();
|
|
4912
|
+
var orchestrationPlanSchema = z2.object({
|
|
4913
|
+
kind: z2.literal("orchestration.plan.v1"),
|
|
4914
|
+
max_parallelism: z2.number().int().positive().optional(),
|
|
4915
|
+
steps: z2.array(
|
|
4916
|
+
z2.object({
|
|
4917
|
+
id: z2.string().min(1).optional(),
|
|
4918
|
+
depends_on: z2.array(z2.string().min(1)).optional(),
|
|
4919
|
+
agents: z2.array(
|
|
4920
|
+
z2.object({
|
|
4921
|
+
id: z2.string().min(1),
|
|
4922
|
+
reason: z2.string().min(1)
|
|
4923
|
+
})
|
|
4924
|
+
).min(1)
|
|
4925
|
+
}).strict()
|
|
4926
|
+
).min(1)
|
|
4927
|
+
}).strict();
|
|
4928
|
+
var PluginLoader = class {
|
|
4929
|
+
constructor(options = {}) {
|
|
4930
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
4931
|
+
this.fetchFn = options.fetch || globalThis.fetch;
|
|
4932
|
+
if (!this.fetchFn) {
|
|
4933
|
+
throw new ConfigError("fetch is required to load plugins");
|
|
4934
|
+
}
|
|
4935
|
+
this.apiBaseUrl = options.apiBaseUrl || DEFAULT_GITHUB_API_BASE;
|
|
4936
|
+
this.rawBaseUrl = options.rawBaseUrl || DEFAULT_GITHUB_RAW_BASE;
|
|
4937
|
+
this.cacheTtlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
4938
|
+
this.now = options.now || (() => /* @__PURE__ */ new Date());
|
|
4939
|
+
}
|
|
4940
|
+
async load(sourceUrl, options = {}) {
|
|
4941
|
+
const ref = parseGitHubPluginRef(sourceUrl);
|
|
4942
|
+
const key = ref.canonical;
|
|
4943
|
+
const cached = this.cache.get(key);
|
|
4944
|
+
if (cached && cached.expiresAt > this.now().getTime()) {
|
|
4945
|
+
return clonePlugin(cached.plugin);
|
|
4946
|
+
}
|
|
4947
|
+
const manifestCandidates = ["PLUGIN.md", "SKILL.md"];
|
|
4948
|
+
let manifestPath = "";
|
|
4949
|
+
let manifestMd = "";
|
|
4950
|
+
for (const candidate of manifestCandidates) {
|
|
4951
|
+
const path = joinRepoPath(ref.repoPath, candidate);
|
|
4952
|
+
const url = this.rawUrl(ref, path);
|
|
4953
|
+
const res = await this.fetchText(url, options.signal);
|
|
4954
|
+
if (res.status === 404) {
|
|
4955
|
+
continue;
|
|
4956
|
+
}
|
|
4957
|
+
if (!res.ok) {
|
|
4958
|
+
throw new ConfigError(`fetch ${path}: ${res.statusText}`);
|
|
4959
|
+
}
|
|
4960
|
+
manifestPath = path;
|
|
4961
|
+
manifestMd = res.body;
|
|
4962
|
+
break;
|
|
4963
|
+
}
|
|
4964
|
+
if (!manifestPath) {
|
|
4965
|
+
throw new ConfigError("plugin manifest not found");
|
|
4966
|
+
}
|
|
4967
|
+
const commandsDir = joinRepoPath(ref.repoPath, "commands");
|
|
4968
|
+
const agentsDir = joinRepoPath(ref.repoPath, "agents");
|
|
4969
|
+
const commandFiles = await this.listMarkdownFiles(ref, commandsDir, options.signal);
|
|
4970
|
+
const agentFiles = await this.listMarkdownFiles(ref, agentsDir, options.signal);
|
|
4971
|
+
const plugin = {
|
|
4972
|
+
id: asPluginId(`${ref.owner}/${ref.repo}${ref.repoPath ? `/${ref.repoPath}` : ""}`),
|
|
4973
|
+
url: asPluginUrl(ref.canonical),
|
|
4974
|
+
manifest: parsePluginManifest(manifestMd),
|
|
4975
|
+
commands: {},
|
|
4976
|
+
agents: {},
|
|
4977
|
+
rawFiles: { [manifestPath]: manifestMd },
|
|
4978
|
+
ref: {
|
|
4979
|
+
owner: ref.owner,
|
|
4980
|
+
repo: ref.repo,
|
|
4981
|
+
ref: ref.ref,
|
|
4982
|
+
path: ref.repoPath || void 0
|
|
4983
|
+
},
|
|
4984
|
+
loadedAt: this.now()
|
|
4985
|
+
};
|
|
4986
|
+
for (const filePath of commandFiles) {
|
|
4987
|
+
const res = await this.fetchText(this.rawUrl(ref, filePath), options.signal);
|
|
4988
|
+
if (!res.ok) {
|
|
4989
|
+
throw new ConfigError(`fetch ${filePath}: ${res.statusText}`);
|
|
4990
|
+
}
|
|
4991
|
+
const { tools, body } = parseMarkdownFrontMatter(res.body);
|
|
4992
|
+
const name = asPluginCommandName(basename(filePath));
|
|
4993
|
+
plugin.commands[String(name)] = {
|
|
4994
|
+
name,
|
|
4995
|
+
prompt: body,
|
|
4996
|
+
agentRefs: extractAgentRefs(body),
|
|
4997
|
+
tools
|
|
4998
|
+
};
|
|
4999
|
+
plugin.rawFiles[filePath] = res.body;
|
|
5000
|
+
}
|
|
5001
|
+
for (const filePath of agentFiles) {
|
|
5002
|
+
const res = await this.fetchText(this.rawUrl(ref, filePath), options.signal);
|
|
5003
|
+
if (!res.ok) {
|
|
5004
|
+
throw new ConfigError(`fetch ${filePath}: ${res.statusText}`);
|
|
5005
|
+
}
|
|
5006
|
+
const { description, tools, body } = parseMarkdownFrontMatter(res.body);
|
|
5007
|
+
const name = asPluginAgentName(basename(filePath));
|
|
5008
|
+
plugin.agents[String(name)] = {
|
|
5009
|
+
name,
|
|
5010
|
+
systemPrompt: body,
|
|
5011
|
+
description,
|
|
5012
|
+
tools
|
|
5013
|
+
};
|
|
5014
|
+
plugin.rawFiles[filePath] = res.body;
|
|
5015
|
+
}
|
|
5016
|
+
plugin.manifest.commands = sortedKeys(Object.values(plugin.commands).map((c) => c.name));
|
|
5017
|
+
plugin.manifest.agents = sortedKeys(Object.values(plugin.agents).map((a) => a.name));
|
|
5018
|
+
this.cache.set(key, {
|
|
5019
|
+
expiresAt: this.now().getTime() + this.cacheTtlMs,
|
|
5020
|
+
plugin: clonePlugin(plugin)
|
|
5021
|
+
});
|
|
5022
|
+
return clonePlugin(plugin);
|
|
5023
|
+
}
|
|
5024
|
+
async listMarkdownFiles(ref, repoDir, signal) {
|
|
5025
|
+
const path = `/repos/${ref.owner}/${ref.repo}/contents/${repoDir}`;
|
|
5026
|
+
const url = `${this.apiBaseUrl}${path}?ref=${encodeURIComponent(ref.ref)}`;
|
|
5027
|
+
const res = await this.fetchJson(url, signal);
|
|
5028
|
+
if (res.status === 404) {
|
|
5029
|
+
return [];
|
|
5030
|
+
}
|
|
5031
|
+
if (!res.ok) {
|
|
5032
|
+
throw new ConfigError(`fetch ${repoDir}: ${res.statusText}`);
|
|
5033
|
+
}
|
|
5034
|
+
return res.body.filter((entry) => entry.type === "file" && entry.name.endsWith(".md")).map((entry) => entry.path);
|
|
5035
|
+
}
|
|
5036
|
+
rawUrl(ref, repoPath) {
|
|
5037
|
+
const cleaned = repoPath.replace(/^\/+/, "");
|
|
5038
|
+
return `${this.rawBaseUrl}/${ref.owner}/${ref.repo}/${ref.ref}/${cleaned}`;
|
|
5039
|
+
}
|
|
5040
|
+
async fetchText(url, signal) {
|
|
5041
|
+
const res = await this.fetchFn(url, { signal });
|
|
5042
|
+
const body = await res.text();
|
|
5043
|
+
return { ok: res.ok, status: res.status, statusText: res.statusText, body };
|
|
5044
|
+
}
|
|
5045
|
+
async fetchJson(url, signal) {
|
|
5046
|
+
const res = await this.fetchFn(url, { signal });
|
|
5047
|
+
if (!res.ok) {
|
|
5048
|
+
return {
|
|
5049
|
+
ok: res.ok,
|
|
5050
|
+
status: res.status,
|
|
5051
|
+
statusText: res.statusText,
|
|
5052
|
+
body: []
|
|
5053
|
+
};
|
|
5054
|
+
}
|
|
5055
|
+
const body = await res.json();
|
|
5056
|
+
return { ok: res.ok, status: res.status, statusText: res.statusText, body };
|
|
5057
|
+
}
|
|
5058
|
+
};
|
|
5059
|
+
var PluginConverter = class {
|
|
5060
|
+
constructor(responses, http, auth, options = {}) {
|
|
5061
|
+
this.responses = responses;
|
|
5062
|
+
this.http = http;
|
|
5063
|
+
this.auth = auth;
|
|
5064
|
+
this.converterModel = asModelId(options.converterModel || "claude-3-5-haiku-latest");
|
|
5065
|
+
}
|
|
5066
|
+
async toWorkflow(plugin, commandName, task) {
|
|
5067
|
+
const command = resolveCommand(plugin, commandName);
|
|
5068
|
+
const prompt = buildPluginConversionPrompt(plugin, command, task);
|
|
5069
|
+
const schemaName = "workflow";
|
|
5070
|
+
const result = await this.responses.object({
|
|
5071
|
+
model: this.converterModel,
|
|
5072
|
+
schema: workflowIntentSchema,
|
|
5073
|
+
schemaName,
|
|
5074
|
+
system: pluginToWorkflowSystemPrompt,
|
|
5075
|
+
prompt
|
|
5076
|
+
});
|
|
5077
|
+
const spec = normalizeWorkflowIntent(result);
|
|
5078
|
+
validateWorkflowTools(spec);
|
|
5079
|
+
return spec;
|
|
5080
|
+
}
|
|
5081
|
+
async toWorkflowDynamic(plugin, commandName, task) {
|
|
5082
|
+
const command = resolveCommand(plugin, commandName);
|
|
5083
|
+
const { candidates, lookup } = buildOrchestrationCandidates(plugin, command);
|
|
5084
|
+
const prompt = buildPluginOrchestrationPrompt(plugin, command, task, candidates);
|
|
5085
|
+
const plan = await this.responses.object({
|
|
5086
|
+
model: this.converterModel,
|
|
5087
|
+
schema: orchestrationPlanSchema,
|
|
5088
|
+
schemaName: "orchestration_plan",
|
|
5089
|
+
system: pluginOrchestrationSystemPrompt,
|
|
5090
|
+
prompt
|
|
5091
|
+
});
|
|
5092
|
+
validateOrchestrationPlan(plan, lookup);
|
|
5093
|
+
const spec = buildDynamicWorkflowFromPlan(plugin, command, task, plan, lookup, this.converterModel);
|
|
5094
|
+
if (specRequiresTools(spec)) {
|
|
5095
|
+
await ensureModelSupportsTools(this.http, this.auth, this.converterModel);
|
|
5096
|
+
}
|
|
5097
|
+
validateWorkflowTools(spec);
|
|
5098
|
+
return spec;
|
|
5099
|
+
}
|
|
5100
|
+
};
|
|
5101
|
+
var PluginRunner = class {
|
|
5102
|
+
constructor(runs) {
|
|
5103
|
+
this.runs = runs;
|
|
5104
|
+
}
|
|
5105
|
+
async run(spec, config) {
|
|
5106
|
+
const created = await this.runs.create(spec, config.runOptions);
|
|
5107
|
+
return this.wait(created.run_id, config);
|
|
5108
|
+
}
|
|
5109
|
+
async wait(runId, config) {
|
|
5110
|
+
const events = [];
|
|
5111
|
+
const toolRegistry = config.toolRegistry;
|
|
5112
|
+
const eventStream = await this.runs.events(runId, config.runOptions);
|
|
5113
|
+
const runner = toolRegistry ? new ToolRunner({ registry: toolRegistry, runsClient: this.runs }) : null;
|
|
5114
|
+
const stream = runner ? runner.processEvents(runId, eventStream) : eventStream;
|
|
5115
|
+
let terminal = null;
|
|
5116
|
+
for await (const event of stream) {
|
|
5117
|
+
events.push(event);
|
|
5118
|
+
if (event.type === "run_completed") {
|
|
5119
|
+
terminal = "succeeded";
|
|
5120
|
+
break;
|
|
5121
|
+
}
|
|
5122
|
+
if (event.type === "run_failed") {
|
|
5123
|
+
terminal = "failed";
|
|
5124
|
+
break;
|
|
5125
|
+
}
|
|
5126
|
+
if (event.type === "run_canceled") {
|
|
5127
|
+
terminal = "canceled";
|
|
5128
|
+
break;
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
const snapshot = await this.runs.get(runId, config.runOptions);
|
|
5132
|
+
return {
|
|
5133
|
+
runId: snapshot.run_id,
|
|
5134
|
+
status: terminal || snapshot.status,
|
|
5135
|
+
outputs: snapshot.outputs,
|
|
5136
|
+
costSummary: snapshot.cost_summary,
|
|
5137
|
+
events
|
|
5138
|
+
};
|
|
5139
|
+
}
|
|
5140
|
+
};
|
|
5141
|
+
var PluginsClient = class {
|
|
5142
|
+
constructor(deps) {
|
|
5143
|
+
this.responses = deps.responses;
|
|
5144
|
+
this.http = deps.http;
|
|
5145
|
+
this.auth = deps.auth;
|
|
5146
|
+
this.loader = new PluginLoader(deps.options);
|
|
5147
|
+
this.converter = new PluginConverter(deps.responses, deps.http, deps.auth);
|
|
5148
|
+
this.runner = new PluginRunner(deps.runs);
|
|
5149
|
+
}
|
|
5150
|
+
load(url, options) {
|
|
5151
|
+
return this.loader.load(url, options);
|
|
5152
|
+
}
|
|
5153
|
+
async run(plugin, command, config) {
|
|
5154
|
+
const task = config.userTask?.trim();
|
|
5155
|
+
if (!task) {
|
|
5156
|
+
throw new ConfigError("userTask is required");
|
|
5157
|
+
}
|
|
5158
|
+
const mode = normalizeOrchestrationMode(config.orchestrationMode);
|
|
5159
|
+
const converterModel = config.converterModel ? asModelId(String(config.converterModel)) : void 0;
|
|
5160
|
+
const converter = converterModel ? new PluginConverter(this.responses, this.http, this.auth, {
|
|
5161
|
+
converterModel
|
|
5162
|
+
}) : this.converter;
|
|
5163
|
+
let spec;
|
|
5164
|
+
if (mode === OrchestrationModes.Dynamic) {
|
|
5165
|
+
spec = await converter.toWorkflowDynamic(plugin, command, task);
|
|
5166
|
+
} else {
|
|
5167
|
+
spec = await converter.toWorkflow(plugin, command, task);
|
|
5168
|
+
}
|
|
5169
|
+
if (config.model) {
|
|
5170
|
+
spec = { ...spec, model: String(config.model) };
|
|
5171
|
+
}
|
|
5172
|
+
return this.runner.run(spec, config);
|
|
5173
|
+
}
|
|
5174
|
+
async quickRun(pluginUrl, command, userTask, config = {}) {
|
|
5175
|
+
const plugin = await this.load(pluginUrl);
|
|
5176
|
+
return this.run(plugin, command, { ...config, userTask });
|
|
5177
|
+
}
|
|
5178
|
+
};
|
|
5179
|
+
function normalizeOrchestrationMode(mode) {
|
|
5180
|
+
if (!mode) return OrchestrationModes.DAG;
|
|
5181
|
+
if (mode !== OrchestrationModes.DAG && mode !== OrchestrationModes.Dynamic) {
|
|
5182
|
+
throw new ConfigError(`invalid orchestration mode: ${mode}`);
|
|
5183
|
+
}
|
|
5184
|
+
return mode;
|
|
5185
|
+
}
|
|
5186
|
+
function resolveCommand(plugin, commandName) {
|
|
5187
|
+
const trimmed = commandName.trim();
|
|
5188
|
+
if (!trimmed) {
|
|
5189
|
+
throw new ConfigError("command is required");
|
|
5190
|
+
}
|
|
5191
|
+
const command = plugin.commands[trimmed];
|
|
5192
|
+
if (!command) {
|
|
5193
|
+
throw new ConfigError("unknown command");
|
|
5194
|
+
}
|
|
5195
|
+
return command;
|
|
5196
|
+
}
|
|
5197
|
+
var pluginToWorkflowSystemPrompt = `You convert a ModelRelay plugin (markdown files) into a single workflow JSON spec.
|
|
5198
|
+
|
|
5199
|
+
Rules:
|
|
5200
|
+
- Output MUST be a single JSON object and MUST validate as workflow.
|
|
5201
|
+
- Do NOT output markdown, commentary, or code fences.
|
|
5202
|
+
- Use a DAG with parallelism when multiple agents are independent.
|
|
5203
|
+
- Use join.all to aggregate parallel branches and then a final synthesizer node.
|
|
5204
|
+
- Use depends_on for edges between nodes.
|
|
5205
|
+
- Bind node outputs using {{placeholders}} when passing data forward.
|
|
5206
|
+
- Tool contract:
|
|
5207
|
+
- Target tools.v0 client tools (see docs/reference/tools.md).
|
|
5208
|
+
- Workspace access MUST use these exact function tool names:
|
|
5209
|
+
- ${Object.values(PluginToolNames).join(", ")}
|
|
5210
|
+
- Prefer fs.* tools for reading/listing/searching the workspace (use bash only when necessary).
|
|
5211
|
+
- Do NOT invent ad-hoc tool names (no repo.*, github.*, filesystem.*, etc.).
|
|
5212
|
+
- All client tools MUST be represented as type="function" tools.
|
|
5213
|
+
- Any node that includes tools MUST set tool_execution.mode="client".
|
|
5214
|
+
- Prefer minimal nodes needed to satisfy the task.
|
|
5215
|
+
`;
|
|
5216
|
+
var pluginOrchestrationSystemPrompt = `You plan which plugin agents to run based only on their descriptions.
|
|
5217
|
+
|
|
5218
|
+
Rules:
|
|
5219
|
+
- Output MUST be a single JSON object that matches orchestration.plan.v1.
|
|
5220
|
+
- Do NOT output markdown, commentary, or code fences.
|
|
5221
|
+
- Select only from the provided agent IDs.
|
|
5222
|
+
- Prefer minimal agents needed to satisfy the user task.
|
|
5223
|
+
- Use multiple steps only when later agents must build on earlier results.
|
|
5224
|
+
- Each step can run agents in parallel.
|
|
5225
|
+
- Use "id" + "depends_on" if you need non-sequential step ordering.
|
|
5226
|
+
`;
|
|
5227
|
+
function buildPluginConversionPrompt(plugin, command, userTask) {
|
|
5228
|
+
const out = [];
|
|
5229
|
+
out.push(`PLUGIN_URL: ${plugin.url}`);
|
|
5230
|
+
out.push(`COMMAND: ${command.name}`);
|
|
5231
|
+
out.push("USER_TASK:");
|
|
5232
|
+
out.push(userTask.trim());
|
|
5233
|
+
out.push("");
|
|
5234
|
+
out.push(`PLUGIN_MANIFEST:`);
|
|
5235
|
+
out.push(JSON.stringify(plugin.manifest));
|
|
5236
|
+
out.push("");
|
|
5237
|
+
out.push(`COMMAND_MARKDOWN (commands/${command.name}.md):`);
|
|
5238
|
+
out.push(command.prompt);
|
|
5239
|
+
out.push("");
|
|
5240
|
+
const agentNames = Object.keys(plugin.agents).sort();
|
|
5241
|
+
if (agentNames.length) {
|
|
5242
|
+
out.push("AGENTS_MARKDOWN:");
|
|
5243
|
+
for (const name of agentNames) {
|
|
5244
|
+
out.push(`---- agents/${name}.md ----`);
|
|
5245
|
+
out.push(plugin.agents[name].systemPrompt);
|
|
5246
|
+
out.push("");
|
|
5247
|
+
}
|
|
5248
|
+
}
|
|
5249
|
+
return out.join("\n");
|
|
5250
|
+
}
|
|
5251
|
+
function buildOrchestrationCandidates(plugin, command) {
|
|
5252
|
+
const names = command.agentRefs?.length ? command.agentRefs : Object.values(plugin.agents).map((agent) => agent.name);
|
|
5253
|
+
if (!names.length) {
|
|
5254
|
+
throw new PluginOrchestrationError(
|
|
5255
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5256
|
+
"no agents available for dynamic orchestration"
|
|
5257
|
+
);
|
|
5258
|
+
}
|
|
5259
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
5260
|
+
const candidates = [];
|
|
5261
|
+
for (const name of names) {
|
|
5262
|
+
const agent = plugin.agents[String(name)];
|
|
5263
|
+
if (!agent) {
|
|
5264
|
+
throw new PluginOrchestrationError(
|
|
5265
|
+
PluginOrchestrationErrorCodes.UnknownAgent,
|
|
5266
|
+
`agent "${name}" not found`
|
|
5267
|
+
);
|
|
5268
|
+
}
|
|
5269
|
+
const desc = agent.description?.trim();
|
|
5270
|
+
if (!desc) {
|
|
5271
|
+
throw new PluginOrchestrationError(
|
|
5272
|
+
PluginOrchestrationErrorCodes.MissingDescription,
|
|
5273
|
+
`agent "${name}" missing description`
|
|
5274
|
+
);
|
|
5275
|
+
}
|
|
5276
|
+
lookup.set(String(name), agent);
|
|
5277
|
+
candidates.push({ name, description: desc, agent });
|
|
5278
|
+
}
|
|
5279
|
+
return { candidates, lookup };
|
|
5280
|
+
}
|
|
5281
|
+
function buildPluginOrchestrationPrompt(plugin, command, userTask, candidates) {
|
|
5282
|
+
const out = [];
|
|
5283
|
+
if (plugin.manifest.name) {
|
|
5284
|
+
out.push(`PLUGIN_NAME: ${plugin.manifest.name}`);
|
|
5285
|
+
}
|
|
5286
|
+
if (plugin.manifest.description) {
|
|
5287
|
+
out.push(`PLUGIN_DESCRIPTION: ${plugin.manifest.description}`);
|
|
5288
|
+
}
|
|
5289
|
+
out.push(`COMMAND: ${command.name}`);
|
|
5290
|
+
out.push("USER_TASK:");
|
|
5291
|
+
out.push(userTask.trim());
|
|
5292
|
+
out.push("");
|
|
5293
|
+
if (command.prompt.trim()) {
|
|
5294
|
+
out.push("COMMAND_MARKDOWN:");
|
|
5295
|
+
out.push(command.prompt);
|
|
5296
|
+
out.push("");
|
|
5297
|
+
}
|
|
5298
|
+
out.push("CANDIDATE_AGENTS:");
|
|
5299
|
+
for (const c of candidates) {
|
|
5300
|
+
out.push(`- id: ${c.name}`);
|
|
5301
|
+
out.push(` description: ${c.description}`);
|
|
5302
|
+
}
|
|
5303
|
+
return out.join("\n");
|
|
5304
|
+
}
|
|
5305
|
+
function validateOrchestrationPlan(plan, lookup) {
|
|
5306
|
+
if (plan.max_parallelism && plan.max_parallelism < 1) {
|
|
5307
|
+
throw new PluginOrchestrationError(
|
|
5308
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5309
|
+
"max_parallelism must be >= 1"
|
|
5310
|
+
);
|
|
5311
|
+
}
|
|
5312
|
+
const stepIds = /* @__PURE__ */ new Map();
|
|
5313
|
+
let hasExplicitDeps = false;
|
|
5314
|
+
plan.steps.forEach((step, idx) => {
|
|
5315
|
+
if (step.depends_on?.length) {
|
|
5316
|
+
hasExplicitDeps = true;
|
|
5317
|
+
}
|
|
5318
|
+
if (step.id) {
|
|
5319
|
+
const key = step.id.trim();
|
|
5320
|
+
if (stepIds.has(key)) {
|
|
5321
|
+
throw new PluginOrchestrationError(
|
|
5322
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5323
|
+
`duplicate step id "${key}"`
|
|
5324
|
+
);
|
|
5325
|
+
}
|
|
5326
|
+
stepIds.set(key, idx);
|
|
5327
|
+
}
|
|
5328
|
+
});
|
|
5329
|
+
if (hasExplicitDeps) {
|
|
5330
|
+
plan.steps.forEach((step) => {
|
|
5331
|
+
if (!step.id?.trim()) {
|
|
5332
|
+
throw new PluginOrchestrationError(
|
|
5333
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5334
|
+
"step id required when depends_on is used"
|
|
5335
|
+
);
|
|
5336
|
+
}
|
|
5337
|
+
});
|
|
5338
|
+
}
|
|
5339
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5340
|
+
plan.steps.forEach((step, idx) => {
|
|
5341
|
+
if (!step.agents.length) {
|
|
5342
|
+
throw new PluginOrchestrationError(
|
|
5343
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5344
|
+
`step ${idx + 1} must include at least one agent`
|
|
5345
|
+
);
|
|
5346
|
+
}
|
|
5347
|
+
if (step.depends_on) {
|
|
5348
|
+
for (const dep of step.depends_on) {
|
|
5349
|
+
const depId = dep.trim();
|
|
5350
|
+
const depIndex = depId ? stepIds.get(depId) : void 0;
|
|
5351
|
+
if (!depId) {
|
|
5352
|
+
throw new PluginOrchestrationError(
|
|
5353
|
+
PluginOrchestrationErrorCodes.InvalidDependency,
|
|
5354
|
+
`step ${idx + 1} has empty depends_on`
|
|
5355
|
+
);
|
|
5356
|
+
}
|
|
5357
|
+
if (depIndex === void 0) {
|
|
5358
|
+
throw new PluginOrchestrationError(
|
|
5359
|
+
PluginOrchestrationErrorCodes.InvalidDependency,
|
|
5360
|
+
`step ${idx + 1} depends on unknown step "${depId}"`
|
|
5361
|
+
);
|
|
5362
|
+
}
|
|
5363
|
+
if (depIndex >= idx) {
|
|
5364
|
+
throw new PluginOrchestrationError(
|
|
5365
|
+
PluginOrchestrationErrorCodes.InvalidDependency,
|
|
5366
|
+
`step ${idx + 1} depends on future step "${depId}"`
|
|
5367
|
+
);
|
|
5368
|
+
}
|
|
5369
|
+
}
|
|
5370
|
+
}
|
|
5371
|
+
for (const agent of step.agents) {
|
|
5372
|
+
const id = agent.id.trim();
|
|
5373
|
+
if (!id) {
|
|
5374
|
+
throw new PluginOrchestrationError(
|
|
5375
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5376
|
+
`step ${idx + 1} agent id required`
|
|
5377
|
+
);
|
|
5378
|
+
}
|
|
5379
|
+
if (!lookup.has(id)) {
|
|
5380
|
+
throw new PluginOrchestrationError(
|
|
5381
|
+
PluginOrchestrationErrorCodes.UnknownAgent,
|
|
5382
|
+
`unknown agent "${id}"`
|
|
5383
|
+
);
|
|
5384
|
+
}
|
|
5385
|
+
if (!agent.reason?.trim()) {
|
|
5386
|
+
throw new PluginOrchestrationError(
|
|
5387
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5388
|
+
`agent "${id}" must include a reason`
|
|
5389
|
+
);
|
|
5390
|
+
}
|
|
5391
|
+
if (seen.has(id)) {
|
|
5392
|
+
throw new PluginOrchestrationError(
|
|
5393
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5394
|
+
`agent "${id}" referenced more than once`
|
|
5395
|
+
);
|
|
5396
|
+
}
|
|
5397
|
+
seen.add(id);
|
|
5398
|
+
}
|
|
5399
|
+
});
|
|
5400
|
+
}
|
|
5401
|
+
function buildDynamicWorkflowFromPlan(plugin, command, userTask, plan, lookup, model) {
|
|
5402
|
+
const stepKeys = plan.steps.map((step, idx) => step.id?.trim() || `step_${idx + 1}`);
|
|
5403
|
+
const stepOrder = new Map(stepKeys.map((key, idx) => [key, idx]));
|
|
5404
|
+
const stepOutputs = /* @__PURE__ */ new Map();
|
|
5405
|
+
const usedNodeIds = /* @__PURE__ */ new Set();
|
|
5406
|
+
const nodes = [];
|
|
5407
|
+
const hasExplicitDeps = plan.steps.some((step) => (step.depends_on?.length ?? 0) > 0);
|
|
5408
|
+
for (let i = 0; i < plan.steps.length; i += 1) {
|
|
5409
|
+
const step = plan.steps[i];
|
|
5410
|
+
const stepKey = stepKeys[i];
|
|
5411
|
+
const dependencyKeys = hasExplicitDeps ? step.depends_on || [] : i > 0 ? [stepKeys[i - 1]] : [];
|
|
5412
|
+
const deps = dependencyKeys.map((raw) => {
|
|
5413
|
+
const key = raw.trim();
|
|
5414
|
+
const nodeId = stepOutputs.get(key);
|
|
5415
|
+
if (!nodeId) {
|
|
5416
|
+
throw new PluginOrchestrationError(
|
|
5417
|
+
PluginOrchestrationErrorCodes.InvalidDependency,
|
|
5418
|
+
`missing output for dependency "${key}"`
|
|
5419
|
+
);
|
|
5420
|
+
}
|
|
5421
|
+
const depIndex = stepOrder.get(key);
|
|
5422
|
+
if (depIndex === void 0 || depIndex >= i) {
|
|
5423
|
+
throw new PluginOrchestrationError(
|
|
5424
|
+
PluginOrchestrationErrorCodes.InvalidDependency,
|
|
5425
|
+
`invalid dependency "${key}"`
|
|
5426
|
+
);
|
|
5427
|
+
}
|
|
5428
|
+
return { stepId: key, nodeId };
|
|
5429
|
+
});
|
|
5430
|
+
const stepNodeIds = [];
|
|
5431
|
+
for (const selection of step.agents) {
|
|
5432
|
+
const agentName = selection.id.trim();
|
|
5433
|
+
const agent = lookup.get(agentName);
|
|
5434
|
+
if (!agent) {
|
|
5435
|
+
throw new PluginOrchestrationError(
|
|
5436
|
+
PluginOrchestrationErrorCodes.UnknownAgent,
|
|
5437
|
+
`unknown agent "${agentName}"`
|
|
5438
|
+
);
|
|
5439
|
+
}
|
|
5440
|
+
const nodeId = parseNodeId(formatAgentNodeId(agentName));
|
|
5441
|
+
if (usedNodeIds.has(nodeId)) {
|
|
5442
|
+
throw new PluginOrchestrationError(
|
|
5443
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5444
|
+
`duplicate node id "${nodeId}"`
|
|
5445
|
+
);
|
|
5446
|
+
}
|
|
5447
|
+
const tools = buildToolRefs(agent, command);
|
|
5448
|
+
const node = {
|
|
5449
|
+
id: nodeId,
|
|
5450
|
+
type: WorkflowNodeTypesIntent.LLM,
|
|
5451
|
+
system: agent.systemPrompt.trim() || void 0,
|
|
5452
|
+
user: buildDynamicAgentUserPrompt(command, userTask, deps),
|
|
5453
|
+
depends_on: deps.length ? deps.map((d) => d.nodeId) : void 0,
|
|
5454
|
+
tools: tools.length ? tools : void 0,
|
|
5455
|
+
tool_execution: tools.length ? { mode: "client" } : void 0
|
|
5456
|
+
};
|
|
5457
|
+
nodes.push(node);
|
|
5458
|
+
stepNodeIds.push(nodeId);
|
|
5459
|
+
usedNodeIds.add(nodeId);
|
|
5460
|
+
}
|
|
5461
|
+
let outputNodeId = stepNodeIds[0];
|
|
5462
|
+
if (stepNodeIds.length > 1) {
|
|
5463
|
+
const joinId = parseNodeId(formatStepJoinNodeId(stepKey));
|
|
5464
|
+
if (usedNodeIds.has(joinId)) {
|
|
5465
|
+
throw new PluginOrchestrationError(
|
|
5466
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5467
|
+
`duplicate node id "${joinId}"`
|
|
5468
|
+
);
|
|
5469
|
+
}
|
|
5470
|
+
nodes.push({
|
|
5471
|
+
id: joinId,
|
|
5472
|
+
type: WorkflowNodeTypesIntent.JoinAll,
|
|
5473
|
+
depends_on: stepNodeIds
|
|
5474
|
+
});
|
|
5475
|
+
usedNodeIds.add(joinId);
|
|
5476
|
+
outputNodeId = joinId;
|
|
5477
|
+
}
|
|
5478
|
+
stepOutputs.set(stepKey, outputNodeId);
|
|
5479
|
+
}
|
|
5480
|
+
const terminalOutputs = findTerminalOutputs(stepKeys, plan, stepOutputs, hasExplicitDeps);
|
|
5481
|
+
const synthId = parseNodeId("orchestrator_synthesize");
|
|
5482
|
+
const synthNode = {
|
|
5483
|
+
id: synthId,
|
|
5484
|
+
type: WorkflowNodeTypesIntent.LLM,
|
|
5485
|
+
user: buildDynamicSynthesisPrompt(command, userTask, terminalOutputs),
|
|
5486
|
+
depends_on: terminalOutputs
|
|
5487
|
+
};
|
|
5488
|
+
const spec = {
|
|
5489
|
+
kind: WorkflowKinds.WorkflowIntent,
|
|
5490
|
+
name: plugin.manifest.name?.trim() || command.name,
|
|
5491
|
+
model: String(model),
|
|
5492
|
+
max_parallelism: plan.max_parallelism,
|
|
5493
|
+
nodes: [...nodes, synthNode],
|
|
5494
|
+
outputs: [{ name: parseOutputName("result"), from: synthId }]
|
|
5495
|
+
};
|
|
5496
|
+
return spec;
|
|
5497
|
+
}
|
|
5498
|
+
function buildDynamicAgentUserPrompt(command, task, deps) {
|
|
5499
|
+
const parts = [];
|
|
5500
|
+
if (command.prompt.trim()) {
|
|
5501
|
+
parts.push(command.prompt.trim());
|
|
5502
|
+
}
|
|
5503
|
+
parts.push("USER_TASK:");
|
|
5504
|
+
parts.push(task.trim());
|
|
5505
|
+
if (deps.length) {
|
|
5506
|
+
parts.push("", "PREVIOUS_STEP_OUTPUTS:");
|
|
5507
|
+
for (const dep of deps) {
|
|
5508
|
+
parts.push(`- ${dep.stepId}: {{${dep.nodeId}}}`);
|
|
5509
|
+
}
|
|
3880
5510
|
}
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
// src/tiers.ts
|
|
3884
|
-
function defaultTierModelId(tier) {
|
|
3885
|
-
const def = tier.models.find((m) => m.is_default);
|
|
3886
|
-
if (def) return def.model_id;
|
|
3887
|
-
if (tier.models.length === 1) return tier.models[0].model_id;
|
|
3888
|
-
return void 0;
|
|
5511
|
+
return parts.join("\n");
|
|
3889
5512
|
}
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
this.hasAccessToken = !!cfg.accessToken?.trim();
|
|
5513
|
+
function buildDynamicSynthesisPrompt(command, task, outputs) {
|
|
5514
|
+
const parts = ["Synthesize the results and complete the task."];
|
|
5515
|
+
if (command.prompt.trim()) {
|
|
5516
|
+
parts.push("", "COMMAND:");
|
|
5517
|
+
parts.push(command.prompt.trim());
|
|
3896
5518
|
}
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
5519
|
+
parts.push("", "USER_TASK:");
|
|
5520
|
+
parts.push(task.trim());
|
|
5521
|
+
if (outputs.length) {
|
|
5522
|
+
parts.push("", "RESULTS:");
|
|
5523
|
+
for (const id of outputs) {
|
|
5524
|
+
parts.push(`- {{${id}}}`);
|
|
3902
5525
|
}
|
|
3903
5526
|
}
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
5527
|
+
return parts.join("\n");
|
|
5528
|
+
}
|
|
5529
|
+
function buildToolRefs(agent, command) {
|
|
5530
|
+
const names = agent.tools?.length ? agent.tools : command.tools?.length ? command.tools : defaultDynamicToolNames;
|
|
5531
|
+
const unique = /* @__PURE__ */ new Set();
|
|
5532
|
+
for (const name of names) {
|
|
5533
|
+
if (!allowedToolSet.has(name)) {
|
|
5534
|
+
throw new PluginOrchestrationError(
|
|
5535
|
+
PluginOrchestrationErrorCodes.UnknownTool,
|
|
5536
|
+
`unknown tool "${name}"`
|
|
3908
5537
|
);
|
|
3909
5538
|
}
|
|
5539
|
+
unique.add(name);
|
|
3910
5540
|
}
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
const
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
5541
|
+
return Array.from(unique.values());
|
|
5542
|
+
}
|
|
5543
|
+
function findTerminalOutputs(stepKeys, plan, outputs, explicit) {
|
|
5544
|
+
if (!explicit) {
|
|
5545
|
+
const lastKey = stepKeys[stepKeys.length - 1];
|
|
5546
|
+
const out = outputs.get(lastKey);
|
|
5547
|
+
return out ? [out] : [];
|
|
5548
|
+
}
|
|
5549
|
+
const depended = /* @__PURE__ */ new Set();
|
|
5550
|
+
plan.steps.forEach((step) => {
|
|
5551
|
+
(step.depends_on || []).forEach((dep) => depended.add(dep.trim()));
|
|
5552
|
+
});
|
|
5553
|
+
return stepKeys.filter((key) => !depended.has(key)).map((key) => outputs.get(key)).filter((value) => Boolean(value));
|
|
5554
|
+
}
|
|
5555
|
+
function formatAgentNodeId(name) {
|
|
5556
|
+
const token = sanitizeNodeToken(name);
|
|
5557
|
+
if (!token) {
|
|
5558
|
+
throw new PluginOrchestrationError(
|
|
5559
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5560
|
+
"agent id must contain alphanumeric characters"
|
|
5561
|
+
);
|
|
3921
5562
|
}
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
5563
|
+
return `agent_${token}`;
|
|
5564
|
+
}
|
|
5565
|
+
function formatStepJoinNodeId(stepKey) {
|
|
5566
|
+
const token = sanitizeNodeToken(stepKey);
|
|
5567
|
+
if (!token) {
|
|
5568
|
+
throw new PluginOrchestrationError(
|
|
5569
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5570
|
+
"step id must contain alphanumeric characters"
|
|
5571
|
+
);
|
|
5572
|
+
}
|
|
5573
|
+
return token.startsWith("step_") ? `${token}_join` : `step_${token}_join`;
|
|
5574
|
+
}
|
|
5575
|
+
function sanitizeNodeToken(raw) {
|
|
5576
|
+
const trimmed = raw.trim();
|
|
5577
|
+
if (!trimmed) return "";
|
|
5578
|
+
let out = "";
|
|
5579
|
+
for (const ch of trimmed) {
|
|
5580
|
+
if (/[a-zA-Z0-9]/.test(ch)) {
|
|
5581
|
+
out += ch.toLowerCase();
|
|
5582
|
+
} else {
|
|
5583
|
+
out += "_";
|
|
3929
5584
|
}
|
|
3930
|
-
|
|
5585
|
+
}
|
|
5586
|
+
out = out.replace(/_+/g, "_");
|
|
5587
|
+
return out.replace(/^_+|_+$/g, "");
|
|
5588
|
+
}
|
|
5589
|
+
async function ensureModelSupportsTools(http, auth, model) {
|
|
5590
|
+
const authHeaders = await auth.authForResponses();
|
|
5591
|
+
const query = encodeURIComponent("tools");
|
|
5592
|
+
const response = await http.json(
|
|
5593
|
+
`/models?capability=${query}`,
|
|
5594
|
+
{
|
|
3931
5595
|
method: "GET",
|
|
3932
|
-
apiKey:
|
|
3933
|
-
|
|
3934
|
-
|
|
5596
|
+
apiKey: authHeaders.apiKey,
|
|
5597
|
+
accessToken: authHeaders.accessToken
|
|
5598
|
+
}
|
|
5599
|
+
);
|
|
5600
|
+
const modelId = String(model).trim();
|
|
5601
|
+
const found = response.models?.some((entry) => entry.model_id?.trim() === modelId);
|
|
5602
|
+
if (!found) {
|
|
5603
|
+
throw new PluginOrchestrationError(
|
|
5604
|
+
PluginOrchestrationErrorCodes.InvalidToolConfig,
|
|
5605
|
+
`model "${modelId}" does not support tool calling`
|
|
5606
|
+
);
|
|
3935
5607
|
}
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
5608
|
+
}
|
|
5609
|
+
function normalizeWorkflowIntent(spec) {
|
|
5610
|
+
const validation = validateWithZod(workflowIntentSchema, spec);
|
|
5611
|
+
if (!validation.success) {
|
|
5612
|
+
throw new ConfigError("workflow intent validation failed");
|
|
5613
|
+
}
|
|
5614
|
+
validateWorkflowTools(spec);
|
|
5615
|
+
return spec;
|
|
5616
|
+
}
|
|
5617
|
+
function validateWorkflowTools(spec) {
|
|
5618
|
+
for (const node of spec.nodes) {
|
|
5619
|
+
if (node.type !== WorkflowNodeTypesIntent.LLM || !node.tools?.length) {
|
|
5620
|
+
continue;
|
|
5621
|
+
}
|
|
5622
|
+
const tools = node.tools || [];
|
|
5623
|
+
let mode = node.tool_execution?.mode;
|
|
5624
|
+
for (const tool of tools) {
|
|
5625
|
+
if (typeof tool !== "string") {
|
|
5626
|
+
throw new ConfigError(`plugin conversion only supports tools.v0 function tools`);
|
|
5627
|
+
}
|
|
5628
|
+
if (!allowedToolSet.has(tool)) {
|
|
5629
|
+
throw new ConfigError(`unsupported tool "${tool}" (plugin conversion targets tools.v0)`);
|
|
5630
|
+
}
|
|
5631
|
+
mode = "client";
|
|
3954
5632
|
}
|
|
3955
|
-
if (
|
|
3956
|
-
throw new ConfigError("
|
|
5633
|
+
if (mode && mode !== "client") {
|
|
5634
|
+
throw new ConfigError(`tool_execution.mode must be "client" for plugin conversion`);
|
|
3957
5635
|
}
|
|
3958
|
-
|
|
3959
|
-
|
|
5636
|
+
node.tool_execution = { mode: "client" };
|
|
5637
|
+
}
|
|
5638
|
+
}
|
|
5639
|
+
function parsePluginManifest(markdown) {
|
|
5640
|
+
const trimmed = markdown.trim();
|
|
5641
|
+
if (!trimmed) return {};
|
|
5642
|
+
const frontMatter = parseManifestFrontMatter(trimmed);
|
|
5643
|
+
if (frontMatter) return frontMatter;
|
|
5644
|
+
const lines = splitLines(trimmed);
|
|
5645
|
+
let name = "";
|
|
5646
|
+
for (const line of lines) {
|
|
5647
|
+
if (line.startsWith("# ")) {
|
|
5648
|
+
name = line.slice(2).trim();
|
|
5649
|
+
break;
|
|
3960
5650
|
}
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
5651
|
+
}
|
|
5652
|
+
let description = "";
|
|
5653
|
+
if (name) {
|
|
5654
|
+
let after = false;
|
|
5655
|
+
for (const line of lines) {
|
|
5656
|
+
const trimmedLine = line.trim();
|
|
5657
|
+
if (trimmedLine.startsWith("# ")) {
|
|
5658
|
+
after = true;
|
|
5659
|
+
continue;
|
|
5660
|
+
}
|
|
5661
|
+
if (!after) continue;
|
|
5662
|
+
if (!trimmedLine) continue;
|
|
5663
|
+
if (trimmedLine.startsWith("## ")) break;
|
|
5664
|
+
description = trimmedLine;
|
|
5665
|
+
break;
|
|
5666
|
+
}
|
|
5667
|
+
}
|
|
5668
|
+
return { name, description };
|
|
5669
|
+
}
|
|
5670
|
+
function parseManifestFrontMatter(markdown) {
|
|
5671
|
+
const lines = splitLines(markdown);
|
|
5672
|
+
if (!lines.length || lines[0].trim() !== "---") return null;
|
|
5673
|
+
const end = lines.findIndex((line, idx) => idx > 0 && line.trim() === "---");
|
|
5674
|
+
if (end === -1) return null;
|
|
5675
|
+
const manifest = {};
|
|
5676
|
+
let currentList = null;
|
|
5677
|
+
for (const line of lines.slice(1, end)) {
|
|
5678
|
+
const raw = line.trim();
|
|
5679
|
+
if (!raw || raw.startsWith("#")) continue;
|
|
5680
|
+
if (raw.startsWith("- ") && currentList) {
|
|
5681
|
+
const item = raw.slice(2).trim();
|
|
5682
|
+
if (item) {
|
|
5683
|
+
if (currentList === "commands") {
|
|
5684
|
+
manifest.commands = [...manifest.commands || [], asPluginCommandName(item)];
|
|
5685
|
+
} else {
|
|
5686
|
+
manifest.agents = [...manifest.agents || [], asPluginAgentName(item)];
|
|
5687
|
+
}
|
|
5688
|
+
}
|
|
5689
|
+
continue;
|
|
5690
|
+
}
|
|
5691
|
+
currentList = null;
|
|
5692
|
+
const [keyRaw, ...rest] = raw.split(":");
|
|
5693
|
+
if (!keyRaw || rest.length === 0) continue;
|
|
5694
|
+
const key = keyRaw.trim().toLowerCase();
|
|
5695
|
+
const val = rest.join(":").trim().replace(/^['"]|['"]$/g, "");
|
|
5696
|
+
if (key === "name") manifest.name = val;
|
|
5697
|
+
if (key === "description") manifest.description = val;
|
|
5698
|
+
if (key === "version") manifest.version = val;
|
|
5699
|
+
if (key === "commands") currentList = "commands";
|
|
5700
|
+
if (key === "agents") currentList = "agents";
|
|
5701
|
+
}
|
|
5702
|
+
manifest.commands = manifest.commands?.sort();
|
|
5703
|
+
manifest.agents = manifest.agents?.sort();
|
|
5704
|
+
return manifest;
|
|
5705
|
+
}
|
|
5706
|
+
function parseMarkdownFrontMatter(markdown) {
|
|
5707
|
+
const trimmed = markdown.trim();
|
|
5708
|
+
if (!trimmed.startsWith("---")) {
|
|
5709
|
+
return { body: markdown };
|
|
5710
|
+
}
|
|
5711
|
+
const lines = splitLines(trimmed);
|
|
5712
|
+
const endIdx = lines.findIndex((line, idx) => idx > 0 && line.trim() === "---");
|
|
5713
|
+
if (endIdx === -1) {
|
|
5714
|
+
return { body: markdown };
|
|
5715
|
+
}
|
|
5716
|
+
let description;
|
|
5717
|
+
let tools;
|
|
5718
|
+
let currentList = null;
|
|
5719
|
+
const toolItems = [];
|
|
5720
|
+
for (const line of lines.slice(1, endIdx)) {
|
|
5721
|
+
const raw = line.trim();
|
|
5722
|
+
if (!raw || raw.startsWith("#")) continue;
|
|
5723
|
+
if (raw.startsWith("- ") && currentList === "tools") {
|
|
5724
|
+
const item = raw.slice(2).trim();
|
|
5725
|
+
if (item) toolItems.push(item);
|
|
5726
|
+
continue;
|
|
5727
|
+
}
|
|
5728
|
+
currentList = null;
|
|
5729
|
+
const [keyRaw, ...rest] = raw.split(":");
|
|
5730
|
+
if (!keyRaw || rest.length === 0) continue;
|
|
5731
|
+
const key = keyRaw.trim().toLowerCase();
|
|
5732
|
+
const val = rest.join(":").trim().replace(/^['"]|['"]$/g, "");
|
|
5733
|
+
if (key === "description") description = val;
|
|
5734
|
+
if (key === "tools") {
|
|
5735
|
+
if (!val) {
|
|
5736
|
+
currentList = "tools";
|
|
5737
|
+
continue;
|
|
3967
5738
|
}
|
|
5739
|
+
toolItems.push(...splitFrontMatterList(val));
|
|
5740
|
+
}
|
|
5741
|
+
}
|
|
5742
|
+
if (toolItems.length) {
|
|
5743
|
+
tools = toolItems.map((item) => parseToolName(item));
|
|
5744
|
+
}
|
|
5745
|
+
const body = lines.slice(endIdx + 1).join("\n").replace(/^[\n\r]+/, "");
|
|
5746
|
+
return { description, tools, body };
|
|
5747
|
+
}
|
|
5748
|
+
function parseToolName(raw) {
|
|
5749
|
+
const val = raw.trim();
|
|
5750
|
+
if (!allowedToolSet.has(val)) {
|
|
5751
|
+
throw new PluginOrchestrationError(
|
|
5752
|
+
PluginOrchestrationErrorCodes.UnknownTool,
|
|
5753
|
+
`unknown tool "${raw}"`
|
|
3968
5754
|
);
|
|
3969
5755
|
}
|
|
3970
|
-
|
|
5756
|
+
return val;
|
|
5757
|
+
}
|
|
5758
|
+
function splitFrontMatterList(raw) {
|
|
5759
|
+
const cleaned = raw.trim().replace(/^\[/, "").replace(/\]$/, "");
|
|
5760
|
+
if (!cleaned) return [];
|
|
5761
|
+
return cleaned.split(",").map((part) => part.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
|
|
5762
|
+
}
|
|
5763
|
+
function extractAgentRefs(markdown) {
|
|
5764
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5765
|
+
const out = [];
|
|
5766
|
+
const lines = splitLines(markdown);
|
|
5767
|
+
for (const line of lines) {
|
|
5768
|
+
const lower = line.toLowerCase();
|
|
5769
|
+
const idx = lower.indexOf("agents/");
|
|
5770
|
+
if (idx === -1) continue;
|
|
5771
|
+
if (!lower.includes(".md", idx)) continue;
|
|
5772
|
+
let seg = line.slice(idx).trim();
|
|
5773
|
+
seg = seg.replace(/^agents\//, "");
|
|
5774
|
+
seg = seg.split(".md")[0];
|
|
5775
|
+
seg = seg.replace(/[`* _]/g, "").trim();
|
|
5776
|
+
if (!seg || seen.has(seg)) continue;
|
|
5777
|
+
seen.add(seg);
|
|
5778
|
+
out.push(asPluginAgentName(seg));
|
|
5779
|
+
}
|
|
5780
|
+
return out.sort();
|
|
5781
|
+
}
|
|
5782
|
+
function splitLines(input) {
|
|
5783
|
+
return input.replace(/\r\n/g, "\n").split("\n");
|
|
5784
|
+
}
|
|
5785
|
+
function basename(path) {
|
|
5786
|
+
const parts = path.split("/");
|
|
5787
|
+
const last = parts[parts.length - 1] || "";
|
|
5788
|
+
return last.replace(/\.md$/i, "");
|
|
5789
|
+
}
|
|
5790
|
+
function joinRepoPath(base, elem) {
|
|
5791
|
+
const clean = (value) => value.replace(/^\/+|\/+$/g, "");
|
|
5792
|
+
const b = clean(base || "");
|
|
5793
|
+
const e = clean(elem || "");
|
|
5794
|
+
if (!b) return e;
|
|
5795
|
+
if (!e) return b;
|
|
5796
|
+
return `${b}/${e}`;
|
|
5797
|
+
}
|
|
5798
|
+
function sortedKeys(items) {
|
|
5799
|
+
return items.slice().sort();
|
|
5800
|
+
}
|
|
5801
|
+
function clonePlugin(plugin) {
|
|
5802
|
+
return {
|
|
5803
|
+
...plugin,
|
|
5804
|
+
manifest: { ...plugin.manifest },
|
|
5805
|
+
commands: { ...plugin.commands },
|
|
5806
|
+
agents: { ...plugin.agents },
|
|
5807
|
+
rawFiles: { ...plugin.rawFiles },
|
|
5808
|
+
loadedAt: new Date(plugin.loadedAt)
|
|
5809
|
+
};
|
|
5810
|
+
}
|
|
5811
|
+
function asPluginId(value) {
|
|
5812
|
+
const trimmed = value.trim();
|
|
5813
|
+
if (!trimmed) throw new ConfigError("plugin id required");
|
|
5814
|
+
return trimmed;
|
|
5815
|
+
}
|
|
5816
|
+
function asPluginUrl(value) {
|
|
5817
|
+
const trimmed = value.trim();
|
|
5818
|
+
if (!trimmed) throw new ConfigError("plugin url required");
|
|
5819
|
+
return trimmed;
|
|
5820
|
+
}
|
|
5821
|
+
function asPluginCommandName(value) {
|
|
5822
|
+
const trimmed = value.trim();
|
|
5823
|
+
if (!trimmed) throw new ConfigError("plugin command name required");
|
|
5824
|
+
return trimmed;
|
|
5825
|
+
}
|
|
5826
|
+
function asPluginAgentName(value) {
|
|
5827
|
+
const trimmed = value.trim();
|
|
5828
|
+
if (!trimmed) throw new ConfigError("plugin agent name required");
|
|
5829
|
+
return trimmed;
|
|
5830
|
+
}
|
|
5831
|
+
function parseGitHubPluginRef(raw) {
|
|
5832
|
+
let url = raw.trim();
|
|
5833
|
+
if (!url) {
|
|
5834
|
+
throw new ConfigError("source url required");
|
|
5835
|
+
}
|
|
5836
|
+
if (url.startsWith("git@github.com:")) {
|
|
5837
|
+
url = `https://github.com/${url.replace("git@github.com:", "")}`;
|
|
5838
|
+
}
|
|
5839
|
+
if (!url.includes("://")) {
|
|
5840
|
+
url = `https://${url}`;
|
|
5841
|
+
}
|
|
5842
|
+
const parsed = new URL(url);
|
|
5843
|
+
const host = parsed.hostname.replace(/^www\./, "").toLowerCase();
|
|
5844
|
+
if (host !== "github.com" && host !== "raw.githubusercontent.com") {
|
|
5845
|
+
throw new ConfigError(`unsupported host: ${parsed.hostname}`);
|
|
5846
|
+
}
|
|
5847
|
+
let ref = parsed.searchParams.get("ref")?.trim() || "";
|
|
5848
|
+
const parts = parsed.pathname.split("/").filter(Boolean);
|
|
5849
|
+
if (parts.length < 2) {
|
|
5850
|
+
throw new ConfigError("invalid github url: expected /owner/repo");
|
|
5851
|
+
}
|
|
5852
|
+
const owner = parts[0];
|
|
5853
|
+
let repoPart = parts[1].replace(/\.git$/i, "");
|
|
5854
|
+
const atIdx = repoPart.indexOf("@");
|
|
5855
|
+
if (atIdx > 0 && atIdx < repoPart.length - 1) {
|
|
5856
|
+
if (!ref) {
|
|
5857
|
+
ref = repoPart.slice(atIdx + 1);
|
|
5858
|
+
}
|
|
5859
|
+
repoPart = repoPart.slice(0, atIdx);
|
|
5860
|
+
}
|
|
5861
|
+
const repo = repoPart;
|
|
5862
|
+
let rest = parts.slice(2);
|
|
5863
|
+
if (host === "github.com" && rest.length >= 2 && (rest[0] === "tree" || rest[0] === "blob")) {
|
|
5864
|
+
if (!ref) {
|
|
5865
|
+
ref = rest[1];
|
|
5866
|
+
}
|
|
5867
|
+
rest = rest.slice(2);
|
|
5868
|
+
}
|
|
5869
|
+
if (host === "raw.githubusercontent.com") {
|
|
5870
|
+
if (!rest.length) {
|
|
5871
|
+
throw new ConfigError("invalid raw github url");
|
|
5872
|
+
}
|
|
5873
|
+
if (!ref) {
|
|
5874
|
+
ref = rest[0];
|
|
5875
|
+
}
|
|
5876
|
+
rest = rest.slice(1);
|
|
5877
|
+
}
|
|
5878
|
+
let repoPath = rest.join("/");
|
|
5879
|
+
repoPath = repoPath.replace(/^\/+|\/+$/g, "");
|
|
5880
|
+
if (/plugin\.md$/i.test(repoPath) || /skill\.md$/i.test(repoPath)) {
|
|
5881
|
+
repoPath = repoPath.split("/").slice(0, -1).join("/");
|
|
5882
|
+
}
|
|
5883
|
+
if (/\.md$/i.test(repoPath)) {
|
|
5884
|
+
const commandsIdx = repoPath.indexOf("/commands/");
|
|
5885
|
+
if (commandsIdx >= 0) {
|
|
5886
|
+
repoPath = repoPath.slice(0, commandsIdx);
|
|
5887
|
+
}
|
|
5888
|
+
const agentsIdx = repoPath.indexOf("/agents/");
|
|
5889
|
+
if (agentsIdx >= 0) {
|
|
5890
|
+
repoPath = repoPath.slice(0, agentsIdx);
|
|
5891
|
+
}
|
|
5892
|
+
repoPath = repoPath.replace(/^\/+|\/+$/g, "");
|
|
5893
|
+
}
|
|
5894
|
+
if (!ref) {
|
|
5895
|
+
ref = DEFAULT_PLUGIN_REF;
|
|
5896
|
+
}
|
|
5897
|
+
const canonical = repoPath ? `github.com/${owner}/${repo}@${ref}/${repoPath}` : `github.com/${owner}/${repo}@${ref}`;
|
|
5898
|
+
return { owner, repo, ref, repoPath, canonical };
|
|
5899
|
+
}
|
|
5900
|
+
function specRequiresTools(spec) {
|
|
5901
|
+
for (const node of spec.nodes) {
|
|
5902
|
+
if (node.type === WorkflowNodeTypesIntent.LLM && node.tools?.length) {
|
|
5903
|
+
return true;
|
|
5904
|
+
}
|
|
5905
|
+
if (node.type === WorkflowNodeTypesIntent.MapFanout && node.subnode) {
|
|
5906
|
+
const sub = node.subnode;
|
|
5907
|
+
if (sub.tools?.length) {
|
|
5908
|
+
return true;
|
|
5909
|
+
}
|
|
5910
|
+
}
|
|
5911
|
+
}
|
|
5912
|
+
return false;
|
|
5913
|
+
}
|
|
3971
5914
|
|
|
3972
5915
|
// src/http.ts
|
|
3973
5916
|
var HTTPClient = class {
|
|
@@ -4357,21 +6300,176 @@ var CustomerResponsesClient = class {
|
|
|
4357
6300
|
mergeCustomerOptions(this.customerId, options)
|
|
4358
6301
|
);
|
|
4359
6302
|
}
|
|
4360
|
-
async streamTextDeltas(system, user, options = {}) {
|
|
4361
|
-
return this.base.streamTextDeltasForCustomer(
|
|
4362
|
-
this.customerId,
|
|
4363
|
-
system,
|
|
4364
|
-
user,
|
|
4365
|
-
mergeCustomerOptions(this.customerId, options)
|
|
4366
|
-
);
|
|
6303
|
+
async streamTextDeltas(system, user, options = {}) {
|
|
6304
|
+
return this.base.streamTextDeltasForCustomer(
|
|
6305
|
+
this.customerId,
|
|
6306
|
+
system,
|
|
6307
|
+
user,
|
|
6308
|
+
mergeCustomerOptions(this.customerId, options)
|
|
6309
|
+
);
|
|
6310
|
+
}
|
|
6311
|
+
};
|
|
6312
|
+
var CustomerScopedModelRelay = class {
|
|
6313
|
+
constructor(responses, customerId, baseUrl) {
|
|
6314
|
+
const normalized = normalizeCustomerId(customerId);
|
|
6315
|
+
this.responses = new CustomerResponsesClient(responses, normalized);
|
|
6316
|
+
this.customerId = normalized;
|
|
6317
|
+
this.baseUrl = baseUrl;
|
|
6318
|
+
}
|
|
6319
|
+
};
|
|
6320
|
+
|
|
6321
|
+
// src/tool_builder.ts
|
|
6322
|
+
function formatZodError(error) {
|
|
6323
|
+
if (error && typeof error === "object" && "issues" in error && Array.isArray(error.issues)) {
|
|
6324
|
+
const issues = error.issues;
|
|
6325
|
+
return issues.map((issue) => {
|
|
6326
|
+
const path = Array.isArray(issue.path) ? issue.path.join(".") : "";
|
|
6327
|
+
const msg = issue.message || "invalid";
|
|
6328
|
+
return path ? `${path}: ${msg}` : msg;
|
|
6329
|
+
}).join("; ");
|
|
6330
|
+
}
|
|
6331
|
+
return String(error);
|
|
6332
|
+
}
|
|
6333
|
+
var ToolBuilder = class {
|
|
6334
|
+
constructor() {
|
|
6335
|
+
this.entries = [];
|
|
6336
|
+
}
|
|
6337
|
+
/**
|
|
6338
|
+
* Add a tool with a Zod schema and handler.
|
|
6339
|
+
*
|
|
6340
|
+
* The handler receives parsed and validated arguments matching the schema.
|
|
6341
|
+
*
|
|
6342
|
+
* @param name - Tool name (must be unique)
|
|
6343
|
+
* @param description - Human-readable description of what the tool does
|
|
6344
|
+
* @param schema - Zod schema for the tool's parameters
|
|
6345
|
+
* @param handler - Function to execute when the tool is called
|
|
6346
|
+
* @returns this for chaining
|
|
6347
|
+
*
|
|
6348
|
+
* @example
|
|
6349
|
+
* ```typescript
|
|
6350
|
+
* tools.add(
|
|
6351
|
+
* "search_web",
|
|
6352
|
+
* "Search the web for information",
|
|
6353
|
+
* z.object({
|
|
6354
|
+
* query: z.string().describe("Search query"),
|
|
6355
|
+
* maxResults: z.number().optional().describe("Max results to return"),
|
|
6356
|
+
* }),
|
|
6357
|
+
* async (args) => {
|
|
6358
|
+
* // args is typed as { query: string; maxResults?: number }
|
|
6359
|
+
* return await searchAPI(args.query, args.maxResults);
|
|
6360
|
+
* }
|
|
6361
|
+
* );
|
|
6362
|
+
* ```
|
|
6363
|
+
*/
|
|
6364
|
+
add(name, description, schema, handler) {
|
|
6365
|
+
const tool = createFunctionToolFromSchema(name, description, schema);
|
|
6366
|
+
this.entries.push({
|
|
6367
|
+
name,
|
|
6368
|
+
description,
|
|
6369
|
+
schema,
|
|
6370
|
+
handler,
|
|
6371
|
+
tool
|
|
6372
|
+
});
|
|
6373
|
+
return this;
|
|
6374
|
+
}
|
|
6375
|
+
/**
|
|
6376
|
+
* Get tool definitions for use with ResponseBuilder.tools().
|
|
6377
|
+
*
|
|
6378
|
+
* @example
|
|
6379
|
+
* ```typescript
|
|
6380
|
+
* const response = await mr.responses.create(
|
|
6381
|
+
* mr.responses.new()
|
|
6382
|
+
* .model("claude-sonnet-4-5")
|
|
6383
|
+
* .tools(tools.definitions())
|
|
6384
|
+
* .user("What's the weather in Paris?")
|
|
6385
|
+
* .build()
|
|
6386
|
+
* );
|
|
6387
|
+
* ```
|
|
6388
|
+
*/
|
|
6389
|
+
definitions() {
|
|
6390
|
+
return this.entries.map((e) => e.tool);
|
|
6391
|
+
}
|
|
6392
|
+
/**
|
|
6393
|
+
* Get a ToolRegistry with all handlers registered.
|
|
6394
|
+
*
|
|
6395
|
+
* The handlers are wrapped to validate arguments against the schema
|
|
6396
|
+
* before invoking the user's handler. If validation fails, a
|
|
6397
|
+
* ToolArgsError is thrown (which ToolRegistry marks as retryable).
|
|
6398
|
+
*
|
|
6399
|
+
* Note: For mr.agent(), pass the ToolBuilder directly instead of calling
|
|
6400
|
+
* registry(). The agent method extracts both definitions and registry.
|
|
6401
|
+
*
|
|
6402
|
+
* @example
|
|
6403
|
+
* ```typescript
|
|
6404
|
+
* const registry = tools.registry();
|
|
6405
|
+
*
|
|
6406
|
+
* // Use with LocalSession (also pass definitions via defaultTools)
|
|
6407
|
+
* const session = mr.sessions.createLocal({
|
|
6408
|
+
* toolRegistry: registry,
|
|
6409
|
+
* defaultTools: tools.definitions(),
|
|
6410
|
+
* defaultModel: "claude-sonnet-4-5",
|
|
6411
|
+
* });
|
|
6412
|
+
* ```
|
|
6413
|
+
*/
|
|
6414
|
+
registry() {
|
|
6415
|
+
const reg = new ToolRegistry();
|
|
6416
|
+
for (const entry of this.entries) {
|
|
6417
|
+
const validatingHandler = async (args, call) => {
|
|
6418
|
+
const result = entry.schema.safeParse(args);
|
|
6419
|
+
if (!result.success) {
|
|
6420
|
+
throw new ToolArgsError(
|
|
6421
|
+
`Invalid arguments for tool '${entry.name}': ${formatZodError(result.error)}`,
|
|
6422
|
+
call.id,
|
|
6423
|
+
entry.name,
|
|
6424
|
+
call.function?.arguments ?? ""
|
|
6425
|
+
);
|
|
6426
|
+
}
|
|
6427
|
+
return entry.handler(result.data, call);
|
|
6428
|
+
};
|
|
6429
|
+
reg.register(entry.name, validatingHandler);
|
|
6430
|
+
}
|
|
6431
|
+
return reg;
|
|
6432
|
+
}
|
|
6433
|
+
/**
|
|
6434
|
+
* Get both definitions and registry.
|
|
6435
|
+
*
|
|
6436
|
+
* Useful when you need both for manual tool handling.
|
|
6437
|
+
*
|
|
6438
|
+
* @example
|
|
6439
|
+
* ```typescript
|
|
6440
|
+
* const { definitions, registry } = tools.build();
|
|
6441
|
+
*
|
|
6442
|
+
* const response = await mr.responses.create(
|
|
6443
|
+
* mr.responses.new()
|
|
6444
|
+
* .model("claude-sonnet-4-5")
|
|
6445
|
+
* .tools(definitions)
|
|
6446
|
+
* .user("What's the weather?")
|
|
6447
|
+
* .build()
|
|
6448
|
+
* );
|
|
6449
|
+
*
|
|
6450
|
+
* if (hasToolCalls(response)) {
|
|
6451
|
+
* const results = await registry.executeAll(response.output[0].toolCalls!);
|
|
6452
|
+
* // ...
|
|
6453
|
+
* }
|
|
6454
|
+
* ```
|
|
6455
|
+
*/
|
|
6456
|
+
build() {
|
|
6457
|
+
return {
|
|
6458
|
+
definitions: this.definitions(),
|
|
6459
|
+
registry: this.registry()
|
|
6460
|
+
};
|
|
6461
|
+
}
|
|
6462
|
+
/**
|
|
6463
|
+
* Get the number of tools defined.
|
|
6464
|
+
*/
|
|
6465
|
+
get size() {
|
|
6466
|
+
return this.entries.length;
|
|
4367
6467
|
}
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
this.
|
|
4373
|
-
this.customerId = normalized;
|
|
4374
|
-
this.baseUrl = baseUrl;
|
|
6468
|
+
/**
|
|
6469
|
+
* Check if a tool is defined.
|
|
6470
|
+
*/
|
|
6471
|
+
has(name) {
|
|
6472
|
+
return this.entries.some((e) => e.name === name);
|
|
4375
6473
|
}
|
|
4376
6474
|
};
|
|
4377
6475
|
|
|
@@ -4550,6 +6648,12 @@ var WorkflowIntentBuilder = class _WorkflowIntentBuilder {
|
|
|
4550
6648
|
model(model) {
|
|
4551
6649
|
return this.with({ model: model.trim() });
|
|
4552
6650
|
}
|
|
6651
|
+
maxParallelism(n) {
|
|
6652
|
+
return this.with({ maxParallelism: n });
|
|
6653
|
+
}
|
|
6654
|
+
inputs(decls) {
|
|
6655
|
+
return this.with({ inputs: decls });
|
|
6656
|
+
}
|
|
4553
6657
|
node(node) {
|
|
4554
6658
|
return this.with({ nodes: [...this.state.nodes, node] });
|
|
4555
6659
|
}
|
|
@@ -4559,15 +6663,15 @@ var WorkflowIntentBuilder = class _WorkflowIntentBuilder {
|
|
|
4559
6663
|
return this.node(configured.build());
|
|
4560
6664
|
}
|
|
4561
6665
|
joinAll(id) {
|
|
4562
|
-
return this.node({ id, type:
|
|
6666
|
+
return this.node({ id, type: WorkflowNodeTypesIntent.JoinAll });
|
|
4563
6667
|
}
|
|
4564
6668
|
joinAny(id, predicate) {
|
|
4565
|
-
return this.node({ id, type:
|
|
6669
|
+
return this.node({ id, type: WorkflowNodeTypesIntent.JoinAny, predicate });
|
|
4566
6670
|
}
|
|
4567
6671
|
joinCollect(id, options) {
|
|
4568
6672
|
return this.node({
|
|
4569
6673
|
id,
|
|
4570
|
-
type:
|
|
6674
|
+
type: WorkflowNodeTypesIntent.JoinCollect,
|
|
4571
6675
|
limit: options.limit,
|
|
4572
6676
|
timeout_ms: options.timeoutMs,
|
|
4573
6677
|
predicate: options.predicate
|
|
@@ -4576,7 +6680,7 @@ var WorkflowIntentBuilder = class _WorkflowIntentBuilder {
|
|
|
4576
6680
|
transformJSON(id, object, merge) {
|
|
4577
6681
|
return this.node({
|
|
4578
6682
|
id,
|
|
4579
|
-
type:
|
|
6683
|
+
type: WorkflowNodeTypesIntent.TransformJSON,
|
|
4580
6684
|
object,
|
|
4581
6685
|
merge
|
|
4582
6686
|
});
|
|
@@ -4584,7 +6688,7 @@ var WorkflowIntentBuilder = class _WorkflowIntentBuilder {
|
|
|
4584
6688
|
mapFanout(id, options) {
|
|
4585
6689
|
return this.node({
|
|
4586
6690
|
id,
|
|
4587
|
-
type:
|
|
6691
|
+
type: WorkflowNodeTypesIntent.MapFanout,
|
|
4588
6692
|
items_from: options.itemsFrom,
|
|
4589
6693
|
items_from_input: options.itemsFromInput,
|
|
4590
6694
|
items_path: options.itemsPath,
|
|
@@ -4605,7 +6709,14 @@ var WorkflowIntentBuilder = class _WorkflowIntentBuilder {
|
|
|
4605
6709
|
...node,
|
|
4606
6710
|
depends_on: node.depends_on ? [...node.depends_on] : void 0
|
|
4607
6711
|
}));
|
|
4608
|
-
const byId = new Map(
|
|
6712
|
+
const byId = /* @__PURE__ */ new Map();
|
|
6713
|
+
for (let idx = 0; idx < nodes.length; idx++) {
|
|
6714
|
+
const id = nodes[idx].id;
|
|
6715
|
+
if (byId.has(id)) {
|
|
6716
|
+
throw new Error(`duplicate node id "${id}"`);
|
|
6717
|
+
}
|
|
6718
|
+
byId.set(id, idx);
|
|
6719
|
+
}
|
|
4609
6720
|
for (const edge of this.state.edges) {
|
|
4610
6721
|
const idx = byId.get(edge.to);
|
|
4611
6722
|
if (idx === void 0) {
|
|
@@ -4621,6 +6732,8 @@ var WorkflowIntentBuilder = class _WorkflowIntentBuilder {
|
|
|
4621
6732
|
kind: WorkflowKinds.WorkflowIntent,
|
|
4622
6733
|
name: this.state.name,
|
|
4623
6734
|
model: this.state.model,
|
|
6735
|
+
max_parallelism: this.state.maxParallelism,
|
|
6736
|
+
inputs: this.state.inputs,
|
|
4624
6737
|
nodes,
|
|
4625
6738
|
outputs: [...this.state.outputs]
|
|
4626
6739
|
};
|
|
@@ -4628,7 +6741,7 @@ var WorkflowIntentBuilder = class _WorkflowIntentBuilder {
|
|
|
4628
6741
|
};
|
|
4629
6742
|
var LLMNodeBuilder = class {
|
|
4630
6743
|
constructor(id) {
|
|
4631
|
-
this.node = { id, type:
|
|
6744
|
+
this.node = { id, type: WorkflowNodeTypesIntent.LLM };
|
|
4632
6745
|
}
|
|
4633
6746
|
system(text) {
|
|
4634
6747
|
this.node.system = text;
|
|
@@ -4665,6 +6778,45 @@ var LLMNodeBuilder = class {
|
|
|
4665
6778
|
function workflowIntent() {
|
|
4666
6779
|
return new WorkflowIntentBuilder();
|
|
4667
6780
|
}
|
|
6781
|
+
function llm(id, configure) {
|
|
6782
|
+
const builder = new LLMNodeBuilder(parseNodeId(id));
|
|
6783
|
+
const configured = configure ? configure(builder) : builder;
|
|
6784
|
+
return configured.build();
|
|
6785
|
+
}
|
|
6786
|
+
function chain(steps, options) {
|
|
6787
|
+
let builder = new WorkflowIntentBuilder();
|
|
6788
|
+
if (options?.name) {
|
|
6789
|
+
builder = builder.name(options.name);
|
|
6790
|
+
}
|
|
6791
|
+
if (options?.model) {
|
|
6792
|
+
builder = builder.model(options.model);
|
|
6793
|
+
}
|
|
6794
|
+
for (const step of steps) {
|
|
6795
|
+
builder = builder.node(step);
|
|
6796
|
+
}
|
|
6797
|
+
for (let i = 1; i < steps.length; i++) {
|
|
6798
|
+
builder = builder.edge(steps[i - 1].id, steps[i].id);
|
|
6799
|
+
}
|
|
6800
|
+
return builder;
|
|
6801
|
+
}
|
|
6802
|
+
function parallel(steps, options) {
|
|
6803
|
+
let builder = new WorkflowIntentBuilder();
|
|
6804
|
+
const joinId = parseNodeId(options?.joinId ?? "join");
|
|
6805
|
+
if (options?.name) {
|
|
6806
|
+
builder = builder.name(options.name);
|
|
6807
|
+
}
|
|
6808
|
+
if (options?.model) {
|
|
6809
|
+
builder = builder.model(options.model);
|
|
6810
|
+
}
|
|
6811
|
+
for (const step of steps) {
|
|
6812
|
+
builder = builder.node(step);
|
|
6813
|
+
}
|
|
6814
|
+
builder = builder.joinAll(joinId);
|
|
6815
|
+
for (const step of steps) {
|
|
6816
|
+
builder = builder.edge(step.id, joinId);
|
|
6817
|
+
}
|
|
6818
|
+
return builder;
|
|
6819
|
+
}
|
|
4668
6820
|
|
|
4669
6821
|
// src/testing.ts
|
|
4670
6822
|
function buildNDJSONResponse(lines, headers = {}, status = 200) {
|
|
@@ -4732,154 +6884,6 @@ function createMockFetchQueue(responses) {
|
|
|
4732
6884
|
return { fetch: fetchImpl, calls };
|
|
4733
6885
|
}
|
|
4734
6886
|
|
|
4735
|
-
// src/tools_runner.ts
|
|
4736
|
-
var ToolRunner = class {
|
|
4737
|
-
constructor(options) {
|
|
4738
|
-
this.registry = options.registry;
|
|
4739
|
-
this.runsClient = options.runsClient;
|
|
4740
|
-
this.customerId = options.customerId;
|
|
4741
|
-
this.onBeforeExecute = options.onBeforeExecute;
|
|
4742
|
-
this.onAfterExecute = options.onAfterExecute;
|
|
4743
|
-
this.onSubmitted = options.onSubmitted;
|
|
4744
|
-
this.onError = options.onError;
|
|
4745
|
-
}
|
|
4746
|
-
/**
|
|
4747
|
-
* Handles a node_waiting event by executing tools and submitting results.
|
|
4748
|
-
*
|
|
4749
|
-
* @param runId - The run ID
|
|
4750
|
-
* @param nodeId - The node ID that is waiting
|
|
4751
|
-
* @param waiting - The waiting state with pending tool calls
|
|
4752
|
-
* @returns The submission response with accepted count and new status
|
|
4753
|
-
*
|
|
4754
|
-
* @example
|
|
4755
|
-
* ```typescript
|
|
4756
|
-
* for await (const event of client.runs.events(runId)) {
|
|
4757
|
-
* if (event.type === "node_waiting") {
|
|
4758
|
-
* const result = await runner.handleNodeWaiting(
|
|
4759
|
-
* runId,
|
|
4760
|
-
* event.node_id,
|
|
4761
|
-
* event.waiting
|
|
4762
|
-
* );
|
|
4763
|
-
* console.log(`Submitted ${result.accepted} results, status: ${result.status}`);
|
|
4764
|
-
* }
|
|
4765
|
-
* }
|
|
4766
|
-
* ```
|
|
4767
|
-
*/
|
|
4768
|
-
async handleNodeWaiting(runId, nodeId, waiting) {
|
|
4769
|
-
const results = [];
|
|
4770
|
-
for (const pending of waiting.pending_tool_calls) {
|
|
4771
|
-
try {
|
|
4772
|
-
await this.onBeforeExecute?.(pending);
|
|
4773
|
-
const toolCall = createToolCall(
|
|
4774
|
-
pending.tool_call.id,
|
|
4775
|
-
pending.tool_call.name,
|
|
4776
|
-
pending.tool_call.arguments
|
|
4777
|
-
);
|
|
4778
|
-
const result = await this.registry.execute(toolCall);
|
|
4779
|
-
results.push(result);
|
|
4780
|
-
await this.onAfterExecute?.(result);
|
|
4781
|
-
} catch (err) {
|
|
4782
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
4783
|
-
await this.onError?.(error, pending);
|
|
4784
|
-
results.push({
|
|
4785
|
-
toolCallId: pending.tool_call.id,
|
|
4786
|
-
toolName: pending.tool_call.name,
|
|
4787
|
-
result: null,
|
|
4788
|
-
error: error.message
|
|
4789
|
-
});
|
|
4790
|
-
}
|
|
4791
|
-
}
|
|
4792
|
-
const response = await this.runsClient.submitToolResults(
|
|
4793
|
-
runId,
|
|
4794
|
-
{
|
|
4795
|
-
node_id: nodeId,
|
|
4796
|
-
step: waiting.step,
|
|
4797
|
-
request_id: waiting.request_id,
|
|
4798
|
-
results: results.map((r) => ({
|
|
4799
|
-
tool_call: {
|
|
4800
|
-
id: r.toolCallId,
|
|
4801
|
-
name: r.toolName
|
|
4802
|
-
},
|
|
4803
|
-
output: r.error ? `Error: ${r.error}` : typeof r.result === "string" ? r.result : JSON.stringify(r.result)
|
|
4804
|
-
}))
|
|
4805
|
-
},
|
|
4806
|
-
{ customerId: this.customerId }
|
|
4807
|
-
);
|
|
4808
|
-
await this.onSubmitted?.(runId, response.accepted, response.status);
|
|
4809
|
-
return {
|
|
4810
|
-
accepted: response.accepted,
|
|
4811
|
-
status: response.status,
|
|
4812
|
-
results
|
|
4813
|
-
};
|
|
4814
|
-
}
|
|
4815
|
-
/**
|
|
4816
|
-
* Processes a stream of run events, automatically handling node_waiting events.
|
|
4817
|
-
*
|
|
4818
|
-
* This is the main entry point for running a workflow with client-side tools.
|
|
4819
|
-
* It yields all events through (including node_waiting after handling).
|
|
4820
|
-
*
|
|
4821
|
-
* @param runId - The run ID to process
|
|
4822
|
-
* @param events - AsyncIterable of run events (from RunsClient.events())
|
|
4823
|
-
* @yields All run events, with node_waiting events handled automatically
|
|
4824
|
-
*
|
|
4825
|
-
* @example
|
|
4826
|
-
* ```typescript
|
|
4827
|
-
* const run = await client.runs.create(workflowSpec);
|
|
4828
|
-
* const eventStream = client.runs.events(run.run_id);
|
|
4829
|
-
*
|
|
4830
|
-
* for await (const event of runner.processEvents(run.run_id, eventStream)) {
|
|
4831
|
-
* switch (event.type) {
|
|
4832
|
-
* case "node_started":
|
|
4833
|
-
* console.log(`Node ${event.node_id} started`);
|
|
4834
|
-
* break;
|
|
4835
|
-
* case "node_succeeded":
|
|
4836
|
-
* console.log(`Node ${event.node_id} succeeded`);
|
|
4837
|
-
* break;
|
|
4838
|
-
* case "run_succeeded":
|
|
4839
|
-
* console.log("Run completed!");
|
|
4840
|
-
* break;
|
|
4841
|
-
* }
|
|
4842
|
-
* }
|
|
4843
|
-
* ```
|
|
4844
|
-
*/
|
|
4845
|
-
async *processEvents(runId, events) {
|
|
4846
|
-
for await (const event of events) {
|
|
4847
|
-
if (event.type === "node_waiting") {
|
|
4848
|
-
const waitingEvent = event;
|
|
4849
|
-
try {
|
|
4850
|
-
await this.handleNodeWaiting(
|
|
4851
|
-
runId,
|
|
4852
|
-
waitingEvent.node_id,
|
|
4853
|
-
waitingEvent.waiting
|
|
4854
|
-
);
|
|
4855
|
-
} catch (err) {
|
|
4856
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
4857
|
-
await this.onError?.(error);
|
|
4858
|
-
throw error;
|
|
4859
|
-
}
|
|
4860
|
-
}
|
|
4861
|
-
yield event;
|
|
4862
|
-
}
|
|
4863
|
-
}
|
|
4864
|
-
/**
|
|
4865
|
-
* Checks if a run event is a node_waiting event.
|
|
4866
|
-
* Utility for filtering events when not using processEvents().
|
|
4867
|
-
*/
|
|
4868
|
-
static isNodeWaiting(event) {
|
|
4869
|
-
return event.type === "node_waiting";
|
|
4870
|
-
}
|
|
4871
|
-
/**
|
|
4872
|
-
* Checks if a run status is terminal (succeeded, failed, or canceled).
|
|
4873
|
-
* Utility for determining when to stop polling.
|
|
4874
|
-
*/
|
|
4875
|
-
static isTerminalStatus(status) {
|
|
4876
|
-
return status === "succeeded" || status === "failed" || status === "canceled";
|
|
4877
|
-
}
|
|
4878
|
-
};
|
|
4879
|
-
function createToolRunner(options) {
|
|
4880
|
-
return new ToolRunner(options);
|
|
4881
|
-
}
|
|
4882
|
-
|
|
4883
6887
|
// src/generated/index.ts
|
|
4884
6888
|
var generated_exports = {};
|
|
4885
6889
|
|
|
@@ -4890,7 +6894,7 @@ __export(workflow_exports, {
|
|
|
4890
6894
|
LLMNodeBuilder: () => LLMNodeBuilder,
|
|
4891
6895
|
LLM_TEXT_OUTPUT: () => LLM_TEXT_OUTPUT,
|
|
4892
6896
|
LLM_USER_MESSAGE_TEXT: () => LLM_USER_MESSAGE_TEXT,
|
|
4893
|
-
|
|
6897
|
+
NodeTypesIntent: () => NodeTypesIntent,
|
|
4894
6898
|
WorkflowIntentBuilder: () => WorkflowIntentBuilder,
|
|
4895
6899
|
parseNodeId: () => parseNodeId,
|
|
4896
6900
|
parseOutputName: () => parseOutputName,
|
|
@@ -4899,17 +6903,17 @@ __export(workflow_exports, {
|
|
|
4899
6903
|
workflowIntent: () => workflowIntent
|
|
4900
6904
|
});
|
|
4901
6905
|
var KindIntent = WorkflowKinds.WorkflowIntent;
|
|
4902
|
-
var
|
|
4903
|
-
LLM:
|
|
4904
|
-
JoinAll:
|
|
4905
|
-
JoinAny:
|
|
4906
|
-
JoinCollect:
|
|
4907
|
-
TransformJSON:
|
|
4908
|
-
MapFanout:
|
|
6906
|
+
var NodeTypesIntent = {
|
|
6907
|
+
LLM: WorkflowNodeTypesIntent.LLM,
|
|
6908
|
+
JoinAll: WorkflowNodeTypesIntent.JoinAll,
|
|
6909
|
+
JoinAny: WorkflowNodeTypesIntent.JoinAny,
|
|
6910
|
+
JoinCollect: WorkflowNodeTypesIntent.JoinCollect,
|
|
6911
|
+
TransformJSON: WorkflowNodeTypesIntent.TransformJSON,
|
|
6912
|
+
MapFanout: WorkflowNodeTypesIntent.MapFanout
|
|
4909
6913
|
};
|
|
4910
6914
|
|
|
4911
6915
|
// src/index.ts
|
|
4912
|
-
var
|
|
6916
|
+
var _ModelRelay = class _ModelRelay {
|
|
4913
6917
|
static fromSecretKey(secretKey, options = {}) {
|
|
4914
6918
|
return new _ModelRelay({ ...options, key: parseSecretKey(secretKey) });
|
|
4915
6919
|
}
|
|
@@ -4958,22 +6962,187 @@ var ModelRelay = class _ModelRelay {
|
|
|
4958
6962
|
});
|
|
4959
6963
|
this.images = new ImagesClient(this.http, auth);
|
|
4960
6964
|
this.sessions = new SessionsClient(this, this.http, auth);
|
|
6965
|
+
this.stateHandles = new StateHandlesClient(this.http, auth);
|
|
4961
6966
|
this.tiers = new TiersClient(this.http, { apiKey, accessToken });
|
|
6967
|
+
this.plugins = new PluginsClient({
|
|
6968
|
+
responses: this.responses,
|
|
6969
|
+
http: this.http,
|
|
6970
|
+
auth,
|
|
6971
|
+
runs: this.runs
|
|
6972
|
+
});
|
|
4962
6973
|
}
|
|
4963
6974
|
forCustomer(customerId) {
|
|
4964
6975
|
return new CustomerScopedModelRelay(this.responses, customerId, this.baseUrl);
|
|
4965
6976
|
}
|
|
6977
|
+
/**
|
|
6978
|
+
* Simple chat completion with system and user prompt.
|
|
6979
|
+
*
|
|
6980
|
+
* Returns the full Response object for access to usage, model, etc.
|
|
6981
|
+
*
|
|
6982
|
+
* @example
|
|
6983
|
+
* ```typescript
|
|
6984
|
+
* const response = await mr.chat("claude-sonnet-4-5", "Hello!");
|
|
6985
|
+
* console.log(response.output);
|
|
6986
|
+
* console.log(response.usage);
|
|
6987
|
+
* ```
|
|
6988
|
+
*
|
|
6989
|
+
* @example With system prompt
|
|
6990
|
+
* ```typescript
|
|
6991
|
+
* const response = await mr.chat("claude-sonnet-4-5", "Explain quantum computing", {
|
|
6992
|
+
* system: "You are a physics professor",
|
|
6993
|
+
* });
|
|
6994
|
+
* ```
|
|
6995
|
+
*/
|
|
6996
|
+
async chat(model, prompt, options = {}) {
|
|
6997
|
+
const { system, customerId, ...reqOptions } = options;
|
|
6998
|
+
let builder = this.responses.new().model(asModelId(model));
|
|
6999
|
+
if (system) {
|
|
7000
|
+
builder = builder.system(system);
|
|
7001
|
+
}
|
|
7002
|
+
builder = builder.user(prompt);
|
|
7003
|
+
if (customerId) {
|
|
7004
|
+
builder = builder.customerId(customerId);
|
|
7005
|
+
}
|
|
7006
|
+
return this.responses.create(builder.build(), reqOptions);
|
|
7007
|
+
}
|
|
7008
|
+
/**
|
|
7009
|
+
* Simple prompt that returns just the text response.
|
|
7010
|
+
*
|
|
7011
|
+
* The most ergonomic way to get a quick answer.
|
|
7012
|
+
*
|
|
7013
|
+
* @example
|
|
7014
|
+
* ```typescript
|
|
7015
|
+
* const answer = await mr.ask("claude-sonnet-4-5", "What is 2 + 2?");
|
|
7016
|
+
* console.log(answer); // "4"
|
|
7017
|
+
* ```
|
|
7018
|
+
*
|
|
7019
|
+
* @example With system prompt
|
|
7020
|
+
* ```typescript
|
|
7021
|
+
* const haiku = await mr.ask("claude-sonnet-4-5", "Write about the ocean", {
|
|
7022
|
+
* system: "You are a poet who only writes haikus",
|
|
7023
|
+
* });
|
|
7024
|
+
* ```
|
|
7025
|
+
*/
|
|
7026
|
+
async ask(model, prompt, options = {}) {
|
|
7027
|
+
const response = await this.chat(model, prompt, options);
|
|
7028
|
+
return extractAssistantText(response.output);
|
|
7029
|
+
}
|
|
7030
|
+
/**
|
|
7031
|
+
* Run an agentic tool loop to completion.
|
|
7032
|
+
*
|
|
7033
|
+
* Runs API calls in a loop until the model stops calling tools
|
|
7034
|
+
* or maxTurns is reached.
|
|
7035
|
+
*
|
|
7036
|
+
* @example
|
|
7037
|
+
* ```typescript
|
|
7038
|
+
* import { z } from "zod";
|
|
7039
|
+
*
|
|
7040
|
+
* const tools = mr.tools()
|
|
7041
|
+
* .add("read_file", "Read a file", z.object({ path: z.string() }), async (args) => {
|
|
7042
|
+
* return fs.readFile(args.path, "utf-8");
|
|
7043
|
+
* })
|
|
7044
|
+
* .add("write_file", "Write a file", z.object({ path: z.string(), content: z.string() }), async (args) => {
|
|
7045
|
+
* await fs.writeFile(args.path, args.content);
|
|
7046
|
+
* return "File written successfully";
|
|
7047
|
+
* });
|
|
7048
|
+
*
|
|
7049
|
+
* const result = await mr.agent("claude-sonnet-4-5", {
|
|
7050
|
+
* tools,
|
|
7051
|
+
* prompt: "Read config.json and add a version field",
|
|
7052
|
+
* });
|
|
7053
|
+
*
|
|
7054
|
+
* console.log(result.output); // Final text response
|
|
7055
|
+
* console.log(result.usage); // Total tokens used
|
|
7056
|
+
* ```
|
|
7057
|
+
*
|
|
7058
|
+
* @example With system prompt and maxTurns
|
|
7059
|
+
* ```typescript
|
|
7060
|
+
* const result = await mr.agent("claude-sonnet-4-5", {
|
|
7061
|
+
* tools,
|
|
7062
|
+
* prompt: "Refactor the auth module",
|
|
7063
|
+
* system: "You are a senior TypeScript developer",
|
|
7064
|
+
* maxTurns: 50, // or ModelRelay.NO_TURN_LIMIT for unlimited
|
|
7065
|
+
* });
|
|
7066
|
+
* ```
|
|
7067
|
+
*/
|
|
7068
|
+
async agent(model, options) {
|
|
7069
|
+
const { definitions, registry } = options.tools.build();
|
|
7070
|
+
const maxTurns = options.maxTurns ?? _ModelRelay.DEFAULT_MAX_TURNS;
|
|
7071
|
+
const modelId = asModelId(model);
|
|
7072
|
+
const input = [];
|
|
7073
|
+
if (options.system) {
|
|
7074
|
+
input.push(createSystemMessage(options.system));
|
|
7075
|
+
}
|
|
7076
|
+
input.push(createUserMessage(options.prompt));
|
|
7077
|
+
const outcome = await runToolLoop({
|
|
7078
|
+
client: this.responses,
|
|
7079
|
+
input,
|
|
7080
|
+
tools: definitions,
|
|
7081
|
+
registry,
|
|
7082
|
+
maxTurns,
|
|
7083
|
+
buildRequest: (builder) => builder.model(modelId)
|
|
7084
|
+
});
|
|
7085
|
+
if (outcome.status !== "complete") {
|
|
7086
|
+
throw new ConfigError("agent tool loop requires a tool registry");
|
|
7087
|
+
}
|
|
7088
|
+
return {
|
|
7089
|
+
output: outcome.output,
|
|
7090
|
+
usage: outcome.usage,
|
|
7091
|
+
response: outcome.response
|
|
7092
|
+
};
|
|
7093
|
+
}
|
|
7094
|
+
/**
|
|
7095
|
+
* Creates a fluent tool builder for defining tools with Zod schemas.
|
|
7096
|
+
*
|
|
7097
|
+
* @example
|
|
7098
|
+
* ```typescript
|
|
7099
|
+
* import { z } from "zod";
|
|
7100
|
+
*
|
|
7101
|
+
* const tools = mr.tools()
|
|
7102
|
+
* .add("get_weather", "Get current weather", z.object({ location: z.string() }), async (args) => {
|
|
7103
|
+
* return { temp: 72, unit: "fahrenheit" };
|
|
7104
|
+
* })
|
|
7105
|
+
* .add("read_file", "Read a file", z.object({ path: z.string() }), async (args) => {
|
|
7106
|
+
* return fs.readFile(args.path, "utf-8");
|
|
7107
|
+
* });
|
|
7108
|
+
*
|
|
7109
|
+
* // Use with agent (pass ToolBuilder directly)
|
|
7110
|
+
* const result = await mr.agent("claude-sonnet-4-5", {
|
|
7111
|
+
* tools,
|
|
7112
|
+
* prompt: "What's the weather in Paris?",
|
|
7113
|
+
* });
|
|
7114
|
+
*
|
|
7115
|
+
* // Or get tool definitions for manual use
|
|
7116
|
+
* const toolDefs = tools.definitions();
|
|
7117
|
+
* ```
|
|
7118
|
+
*/
|
|
7119
|
+
tools() {
|
|
7120
|
+
return new ToolBuilder();
|
|
7121
|
+
}
|
|
4966
7122
|
};
|
|
7123
|
+
// =========================================================================
|
|
7124
|
+
// Convenience Methods (Simple Case Simple)
|
|
7125
|
+
// =========================================================================
|
|
7126
|
+
/** Default maximum turns for agent loops. */
|
|
7127
|
+
_ModelRelay.DEFAULT_MAX_TURNS = 100;
|
|
7128
|
+
/**
|
|
7129
|
+
* Use this for maxTurns to disable the turn limit.
|
|
7130
|
+
* Use with caution as this can lead to infinite loops and runaway API costs.
|
|
7131
|
+
*/
|
|
7132
|
+
_ModelRelay.NO_TURN_LIMIT = Number.MAX_SAFE_INTEGER;
|
|
7133
|
+
var ModelRelay = _ModelRelay;
|
|
4967
7134
|
function resolveBaseUrl(override) {
|
|
4968
7135
|
const base = override || DEFAULT_BASE_URL;
|
|
4969
7136
|
return base.replace(/\/+$/, "");
|
|
4970
7137
|
}
|
|
4971
7138
|
export {
|
|
4972
7139
|
APIError,
|
|
7140
|
+
AgentMaxTurnsError,
|
|
4973
7141
|
AuthClient,
|
|
4974
7142
|
BillingProviders,
|
|
4975
7143
|
ConfigError,
|
|
4976
7144
|
ContentPartTypes,
|
|
7145
|
+
ContextManager,
|
|
4977
7146
|
CustomerResponsesClient,
|
|
4978
7147
|
CustomerScopedModelRelay,
|
|
4979
7148
|
CustomerTokenProvider,
|
|
@@ -4982,6 +7151,7 @@ export {
|
|
|
4982
7151
|
DEFAULT_CONNECT_TIMEOUT_MS,
|
|
4983
7152
|
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
4984
7153
|
ErrorCodes,
|
|
7154
|
+
FileConversationStore,
|
|
4985
7155
|
ImagesClient,
|
|
4986
7156
|
InputItemTypes,
|
|
4987
7157
|
JoinOutput,
|
|
@@ -5002,19 +7172,29 @@ export {
|
|
|
5002
7172
|
LLM_TEXT_OUTPUT,
|
|
5003
7173
|
LLM_USER_MESSAGE_TEXT,
|
|
5004
7174
|
LocalSession,
|
|
5005
|
-
|
|
7175
|
+
MAX_STATE_HANDLE_TTL_SECONDS,
|
|
7176
|
+
MemoryConversationStore,
|
|
5006
7177
|
MessageRoles,
|
|
5007
7178
|
ModelRelay,
|
|
5008
7179
|
ModelRelayError,
|
|
7180
|
+
OrchestrationModes,
|
|
5009
7181
|
OutputFormatTypes,
|
|
5010
7182
|
OutputItemTypes,
|
|
5011
7183
|
PathEscapeError,
|
|
7184
|
+
PluginConverter,
|
|
7185
|
+
PluginLoader,
|
|
7186
|
+
PluginOrchestrationError,
|
|
7187
|
+
PluginOrchestrationErrorCodes,
|
|
7188
|
+
PluginRunner,
|
|
7189
|
+
PluginToolNames,
|
|
7190
|
+
PluginsClient,
|
|
5012
7191
|
ResponsesClient,
|
|
5013
7192
|
ResponsesStream,
|
|
5014
7193
|
RunsClient,
|
|
5015
7194
|
RunsEventStream,
|
|
5016
7195
|
SDK_VERSION,
|
|
5017
7196
|
SessionsClient,
|
|
7197
|
+
SqliteConversationStore,
|
|
5018
7198
|
StopReasons,
|
|
5019
7199
|
StreamProtocolError,
|
|
5020
7200
|
StreamTimeoutError,
|
|
@@ -5025,18 +7205,19 @@ export {
|
|
|
5025
7205
|
TiersClient,
|
|
5026
7206
|
ToolArgsError,
|
|
5027
7207
|
ToolArgumentError,
|
|
7208
|
+
ToolBuilder,
|
|
5028
7209
|
ToolCallAccumulator,
|
|
5029
7210
|
ToolChoiceTypes,
|
|
5030
7211
|
ToolRegistry,
|
|
5031
7212
|
ToolRunner,
|
|
5032
7213
|
ToolTypes,
|
|
5033
7214
|
TransportError,
|
|
7215
|
+
USER_ASK_TOOL_NAME,
|
|
5034
7216
|
WORKFLOWS_COMPILE_PATH,
|
|
5035
|
-
WebToolIntents,
|
|
5036
7217
|
WorkflowIntentBuilder,
|
|
5037
7218
|
WorkflowKinds,
|
|
5038
|
-
|
|
5039
|
-
|
|
7219
|
+
WorkflowNodeTypesIntent as WorkflowNodeTypes,
|
|
7220
|
+
WorkflowNodeTypesIntent,
|
|
5040
7221
|
WorkflowValidationError,
|
|
5041
7222
|
WorkflowsClient,
|
|
5042
7223
|
asModelId,
|
|
@@ -5046,22 +7227,26 @@ export {
|
|
|
5046
7227
|
assistantMessageWithToolCalls,
|
|
5047
7228
|
buildDelayedNDJSONResponse,
|
|
5048
7229
|
buildNDJSONResponse,
|
|
7230
|
+
chain,
|
|
5049
7231
|
createAccessTokenAuth,
|
|
5050
7232
|
createApiKeyAuth,
|
|
5051
7233
|
createAssistantMessage,
|
|
7234
|
+
createFileConversationStore,
|
|
5052
7235
|
createFunctionCall,
|
|
5053
7236
|
createFunctionTool,
|
|
5054
|
-
createFunctionToolFromSchema,
|
|
5055
7237
|
createLocalSession,
|
|
5056
|
-
|
|
7238
|
+
createMemoryConversationStore,
|
|
5057
7239
|
createMockFetchQueue,
|
|
7240
|
+
createModelContextResolver,
|
|
5058
7241
|
createRetryMessages,
|
|
7242
|
+
createSqliteConversationStore,
|
|
5059
7243
|
createSystemMessage,
|
|
5060
7244
|
createToolCall,
|
|
5061
7245
|
createToolRunner,
|
|
7246
|
+
createTypedTool,
|
|
5062
7247
|
createUsage,
|
|
7248
|
+
createUserAskTool,
|
|
5063
7249
|
createUserMessage,
|
|
5064
|
-
createWebTool,
|
|
5065
7250
|
defaultRetryHandler,
|
|
5066
7251
|
defaultTierModelId,
|
|
5067
7252
|
executeWithRetry,
|
|
@@ -5069,16 +7254,26 @@ export {
|
|
|
5069
7254
|
formatToolErrorForModel,
|
|
5070
7255
|
generateSessionId,
|
|
5071
7256
|
generated_exports as generated,
|
|
7257
|
+
getAllToolCalls,
|
|
7258
|
+
getAssistantText,
|
|
5072
7259
|
getRetryableErrors,
|
|
7260
|
+
getToolArgs,
|
|
7261
|
+
getToolArgsRaw,
|
|
7262
|
+
getToolName,
|
|
7263
|
+
getTypedToolCall,
|
|
7264
|
+
getTypedToolCalls,
|
|
5073
7265
|
hasRetryableErrors,
|
|
5074
7266
|
hasToolCalls,
|
|
5075
7267
|
isSecretKey,
|
|
7268
|
+
isUserAskToolCall,
|
|
7269
|
+
llm,
|
|
5076
7270
|
mergeMetrics,
|
|
5077
7271
|
mergeTrace,
|
|
5078
7272
|
modelToString,
|
|
5079
7273
|
normalizeModelId,
|
|
5080
7274
|
normalizeStopReason,
|
|
5081
7275
|
outputFormatFromZod,
|
|
7276
|
+
parallel,
|
|
5082
7277
|
parseApiKey,
|
|
5083
7278
|
parseErrorResponse,
|
|
5084
7279
|
parseNodeId,
|
|
@@ -5086,15 +7281,20 @@ export {
|
|
|
5086
7281
|
parsePlanHash,
|
|
5087
7282
|
parseRunId,
|
|
5088
7283
|
parseSecretKey,
|
|
5089
|
-
|
|
5090
|
-
|
|
7284
|
+
parseTypedToolCall,
|
|
7285
|
+
parseUserAskArgs,
|
|
7286
|
+
prepareInputWithContext,
|
|
5091
7287
|
respondToToolCall,
|
|
7288
|
+
runToolLoop,
|
|
7289
|
+
serializeUserAskResult,
|
|
5092
7290
|
stopReasonToString,
|
|
5093
7291
|
toolChoiceAuto,
|
|
5094
7292
|
toolChoiceNone,
|
|
5095
7293
|
toolChoiceRequired,
|
|
5096
7294
|
toolResultMessage,
|
|
5097
|
-
|
|
7295
|
+
truncateInputByTokens,
|
|
7296
|
+
userAskResultChoice,
|
|
7297
|
+
userAskResultFreeform,
|
|
5098
7298
|
validateWithZod,
|
|
5099
7299
|
workflow_exports as workflow,
|
|
5100
7300
|
workflowIntent,
|