@modelrelay/sdk 5.3.0 → 8.1.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 +122 -0
- package/dist/{api-7TVb2cnl.d.cts → api-B7SmXjnr.d.cts} +599 -1
- package/dist/{api-7TVb2cnl.d.ts → api-B7SmXjnr.d.ts} +599 -1
- package/dist/api-CdHqjsU_.d.cts +6062 -0
- package/dist/api-CdHqjsU_.d.ts +6062 -0
- package/dist/api-CyuI9lx0.d.cts +6249 -0
- package/dist/api-CyuI9lx0.d.ts +6249 -0
- package/dist/api-DP9MoKHy.d.cts +5993 -0
- package/dist/api-DP9MoKHy.d.ts +5993 -0
- package/dist/api-R_gUMD1L.d.cts +6243 -0
- package/dist/api-R_gUMD1L.d.ts +6243 -0
- package/dist/{api-BRAJe_xm.d.cts → api-c1j5ycVR.d.cts} +0 -54
- package/dist/{api-BRAJe_xm.d.ts → api-c1j5ycVR.d.ts} +0 -54
- 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-5O4NJXLJ.js → chunk-HHBAD7FF.js} +1 -1
- package/dist/{chunk-LZDGY24E.js → chunk-PKGWFDGU.js} +111 -59
- package/dist/{chunk-HLJAMT7F.js → chunk-RVHKBQ7X.js} +1 -1
- package/dist/chunk-SJC7Q4NK.js +1199 -0
- package/dist/chunk-URFLODQC.js +1199 -0
- package/dist/{chunk-JZRSCFQH.js → chunk-YQWOQ74P.js} +53 -20
- package/dist/index.cjs +3468 -1156
- package/dist/index.d.cts +916 -85
- package/dist/index.d.ts +916 -85
- package/dist/index.js +3106 -881
- package/dist/node.cjs +11 -7
- package/dist/node.d.cts +11 -11
- package/dist/node.d.ts +11 -11
- package/dist/node.js +6 -6
- package/dist/{tools-Db-F5rIL.d.cts → tools-Bxdv0Np2.d.cts} +101 -218
- package/dist/{tools-Db-F5rIL.d.ts → tools-Bxdv0Np2.d.ts} +101 -218
- 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/api-B9x3HA3Z.d.cts +0 -5292
- package/dist/api-B9x3HA3Z.d.ts +0 -5292
- package/dist/api-Bitsm1tl.d.cts +0 -5290
- package/dist/api-Bitsm1tl.d.ts +0 -5290
- package/dist/api-D0wnVpwn.d.cts +0 -5292
- package/dist/api-D0wnVpwn.d.ts +0 -5292
- package/dist/api-HVh8Lusf.d.cts +0 -5300
- package/dist/api-HVh8Lusf.d.ts +0 -5300
- package/dist/chunk-BL7GWXRZ.js +0 -1196
- package/dist/chunk-CV3DTA6P.js +0 -1196
- package/dist/chunk-G5H7EY4F.js +0 -1196
- package/dist/chunk-OJFVI3QJ.js +0 -1133
- package/dist/chunk-Z6R4G2TU.js +0 -1133
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-URFLODQC.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,235 +2782,210 @@ 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
|
-
|
|
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
|
+
}
|
|
2542
2811
|
}
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
);
|
|
2812
|
+
return this.request("POST", STATE_HANDLES_PATH, request);
|
|
2813
|
+
}
|
|
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");
|
|
2549
2818
|
}
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
}
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
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);
|
|
2557
2831
|
}
|
|
2558
|
-
|
|
2559
|
-
|
|
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}`);
|
|
2560
2837
|
}
|
|
2561
|
-
|
|
2562
|
-
|
|
2838
|
+
};
|
|
2839
|
+
|
|
2840
|
+
// src/messages.ts
|
|
2841
|
+
var MESSAGES_PATH = "/messages";
|
|
2842
|
+
var MessagesClient = class {
|
|
2843
|
+
constructor(http, auth) {
|
|
2844
|
+
this.http = http;
|
|
2845
|
+
this.auth = auth;
|
|
2563
2846
|
}
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2847
|
+
async request(method, path, body) {
|
|
2848
|
+
const auth = await this.auth.authForResponses();
|
|
2849
|
+
return this.http.json(path, {
|
|
2850
|
+
method,
|
|
2851
|
+
body,
|
|
2852
|
+
apiKey: auth.apiKey,
|
|
2853
|
+
accessToken: auth.accessToken
|
|
2854
|
+
});
|
|
2569
2855
|
}
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
model: modelId,
|
|
2582
|
-
originalMessages: messages.length,
|
|
2583
|
-
keptMessages: truncated.length,
|
|
2584
|
-
maxHistoryTokens: budget.maxHistoryTokens,
|
|
2585
|
-
reservedOutputTokens: budget.reservedOutputTokens
|
|
2586
|
-
};
|
|
2587
|
-
options.onContextTruncate(info);
|
|
2856
|
+
async send(request) {
|
|
2857
|
+
if (!request?.to?.trim()) {
|
|
2858
|
+
throw new Error("to is required");
|
|
2859
|
+
}
|
|
2860
|
+
if (!request?.subject?.trim()) {
|
|
2861
|
+
throw new Error("subject is required");
|
|
2862
|
+
}
|
|
2863
|
+
if (request.body === void 0 || request.body === null) {
|
|
2864
|
+
throw new Error("body is required");
|
|
2865
|
+
}
|
|
2866
|
+
return this.request("POST", MESSAGES_PATH, request);
|
|
2588
2867
|
}
|
|
2589
|
-
|
|
2590
|
-
}
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
}
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2868
|
+
async list(options = {}) {
|
|
2869
|
+
const { to, threadId, unread, limit, offset } = options;
|
|
2870
|
+
if (!to?.trim() && !threadId?.trim()) {
|
|
2871
|
+
throw new Error("to or threadId is required");
|
|
2872
|
+
}
|
|
2873
|
+
if (limit !== void 0 && (limit <= 0 || limit > 200)) {
|
|
2874
|
+
throw new Error("limit must be between 1 and 200");
|
|
2875
|
+
}
|
|
2876
|
+
if (offset !== void 0 && offset < 0) {
|
|
2877
|
+
throw new Error("offset must be non-negative");
|
|
2878
|
+
}
|
|
2879
|
+
const query = new URLSearchParams();
|
|
2880
|
+
if (to?.trim()) {
|
|
2881
|
+
query.set("to", to.trim());
|
|
2882
|
+
}
|
|
2883
|
+
if (threadId?.trim()) {
|
|
2884
|
+
query.set("thread_id", threadId.trim());
|
|
2885
|
+
}
|
|
2886
|
+
if (unread !== void 0) {
|
|
2887
|
+
query.set("unread", String(unread));
|
|
2888
|
+
}
|
|
2889
|
+
if (limit !== void 0) {
|
|
2890
|
+
query.set("limit", String(limit));
|
|
2891
|
+
}
|
|
2892
|
+
if (offset !== void 0 && offset > 0) {
|
|
2893
|
+
query.set("offset", String(offset));
|
|
2894
|
+
}
|
|
2895
|
+
const path = query.toString() ? `${MESSAGES_PATH}?${query.toString()}` : MESSAGES_PATH;
|
|
2896
|
+
return this.request("GET", path);
|
|
2610
2897
|
}
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
"
|
|
2614
|
-
|
|
2898
|
+
async get(messageId) {
|
|
2899
|
+
if (!messageId?.trim()) {
|
|
2900
|
+
throw new Error("messageId is required");
|
|
2901
|
+
}
|
|
2902
|
+
return this.request("GET", `${MESSAGES_PATH}/${messageId}`);
|
|
2615
2903
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
if (selected.has(i)) continue;
|
|
2620
|
-
const tokens = tokensByIndex[i];
|
|
2621
|
-
if (tokens <= remaining) {
|
|
2622
|
-
selected.add(i);
|
|
2623
|
-
remaining -= tokens;
|
|
2904
|
+
async markRead(messageId) {
|
|
2905
|
+
if (!messageId?.trim()) {
|
|
2906
|
+
throw new Error("messageId is required");
|
|
2624
2907
|
}
|
|
2908
|
+
await this.request("POST", `${MESSAGES_PATH}/${messageId}/read`);
|
|
2625
2909
|
}
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2910
|
+
};
|
|
2911
|
+
|
|
2912
|
+
// src/tool_loop.ts
|
|
2913
|
+
var DEFAULT_MAX_TURNS = 100;
|
|
2914
|
+
async function runToolLoop(config) {
|
|
2915
|
+
const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
2916
|
+
if (!Number.isFinite(maxTurns) || maxTurns <= 0) {
|
|
2917
|
+
throw new ConfigError("maxTurns must be a positive number");
|
|
2918
|
+
}
|
|
2919
|
+
const tools = config.tools ?? [];
|
|
2920
|
+
const history = config.input.slice();
|
|
2921
|
+
const usage = {
|
|
2922
|
+
inputTokens: 0,
|
|
2923
|
+
outputTokens: 0,
|
|
2924
|
+
totalTokens: 0,
|
|
2925
|
+
llmCalls: 0,
|
|
2926
|
+
toolCalls: 0
|
|
2927
|
+
};
|
|
2928
|
+
for (let turn = 0; turn < maxTurns; turn += 1) {
|
|
2929
|
+
let builder = config.client.new().input(history);
|
|
2930
|
+
if (tools.length > 0) {
|
|
2931
|
+
builder = builder.tools(tools);
|
|
2932
|
+
}
|
|
2933
|
+
if (config.buildRequest) {
|
|
2934
|
+
builder = config.buildRequest(builder);
|
|
2935
|
+
}
|
|
2936
|
+
const response = await config.client.create(
|
|
2937
|
+
builder.build(),
|
|
2938
|
+
config.requestOptions
|
|
2939
|
+
);
|
|
2940
|
+
usage.llmCalls += 1;
|
|
2941
|
+
usage.inputTokens += response.usage.inputTokens;
|
|
2942
|
+
usage.outputTokens += response.usage.outputTokens;
|
|
2943
|
+
usage.totalTokens += response.usage.totalTokens;
|
|
2944
|
+
const toolCalls = getAllToolCalls(response);
|
|
2945
|
+
if (toolCalls.length === 0) {
|
|
2946
|
+
const assistantText = getAssistantText(response);
|
|
2947
|
+
if (assistantText) {
|
|
2948
|
+
history.push(createAssistantMessage(assistantText));
|
|
2949
|
+
}
|
|
2950
|
+
return {
|
|
2951
|
+
status: "complete",
|
|
2952
|
+
output: assistantText,
|
|
2953
|
+
response,
|
|
2954
|
+
usage,
|
|
2955
|
+
input: history,
|
|
2956
|
+
turnsUsed: turn + 1
|
|
2957
|
+
};
|
|
2958
|
+
}
|
|
2959
|
+
usage.toolCalls += toolCalls.length;
|
|
2960
|
+
history.push(
|
|
2961
|
+
assistantMessageWithToolCalls(getAssistantText(response), toolCalls)
|
|
2962
|
+
);
|
|
2963
|
+
if (!config.registry) {
|
|
2964
|
+
return {
|
|
2965
|
+
status: "waiting_for_tools",
|
|
2966
|
+
pendingToolCalls: toolCalls,
|
|
2967
|
+
response,
|
|
2968
|
+
usage,
|
|
2969
|
+
input: history,
|
|
2970
|
+
turnsUsed: turn + 1
|
|
2971
|
+
};
|
|
2972
|
+
}
|
|
2973
|
+
const results = await config.registry.executeAll(toolCalls);
|
|
2974
|
+
history.push(...config.registry.resultsToMessages(results));
|
|
2629
2975
|
}
|
|
2630
|
-
|
|
2631
|
-
}
|
|
2632
|
-
function estimateTokens(text) {
|
|
2633
|
-
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
2976
|
+
throw new AgentMaxTurnsError(maxTurns);
|
|
2634
2977
|
}
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
return
|
|
2978
|
+
|
|
2979
|
+
// src/sessions/types.ts
|
|
2980
|
+
function asSessionId(value) {
|
|
2981
|
+
return value;
|
|
2639
2982
|
}
|
|
2640
|
-
function
|
|
2641
|
-
|
|
2642
|
-
if (detail === "low") return IMAGE_TOKENS_LOW_DETAIL;
|
|
2643
|
-
return IMAGE_TOKENS_HIGH_DETAIL;
|
|
2644
|
-
}
|
|
2645
|
-
function estimateTokensForMessage(message) {
|
|
2646
|
-
const segments = [message.role];
|
|
2647
|
-
let imageTokens = 0;
|
|
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
|
-
}
|
|
2654
|
-
}
|
|
2655
|
-
if (message.toolCalls) {
|
|
2656
|
-
for (const call of message.toolCalls) {
|
|
2657
|
-
if (call.function?.name) segments.push(call.function.name);
|
|
2658
|
-
if (call.function?.arguments) segments.push(call.function.arguments);
|
|
2659
|
-
}
|
|
2660
|
-
}
|
|
2661
|
-
if (message.toolCallId) {
|
|
2662
|
-
segments.push(message.toolCallId);
|
|
2663
|
-
}
|
|
2664
|
-
const textTokens = estimateTokens(segments.join("\n"));
|
|
2665
|
-
const toolOverhead = message.toolCalls ? message.toolCalls.length * TOOL_CALL_OVERHEAD_TOKENS : 0;
|
|
2666
|
-
return textTokens + MESSAGE_OVERHEAD_TOKENS + toolOverhead + imageTokens;
|
|
2667
|
-
}
|
|
2668
|
-
function normalizePositiveInt(value, label) {
|
|
2669
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
2670
|
-
throw new ConfigError(`${label} must be a positive number`);
|
|
2671
|
-
}
|
|
2672
|
-
return Math.floor(value);
|
|
2673
|
-
}
|
|
2674
|
-
function sumTokens(tokensByIndex, indices) {
|
|
2675
|
-
return indices.reduce((sum, idx) => sum + tokensByIndex[idx], 0);
|
|
2676
|
-
}
|
|
2677
|
-
async function resolveHistoryBudget(modelId, options, resolveModelContext) {
|
|
2678
|
-
const reservedOutputTokens = options.reserveOutputTokens === void 0 ? void 0 : normalizeNonNegativeInt(
|
|
2679
|
-
options.reserveOutputTokens,
|
|
2680
|
-
"reserveOutputTokens"
|
|
2681
|
-
);
|
|
2682
|
-
if (options.maxHistoryTokens !== void 0) {
|
|
2683
|
-
return {
|
|
2684
|
-
maxHistoryTokens: normalizePositiveInt(
|
|
2685
|
-
options.maxHistoryTokens,
|
|
2686
|
-
"maxHistoryTokens"
|
|
2687
|
-
),
|
|
2688
|
-
reservedOutputTokens
|
|
2689
|
-
};
|
|
2690
|
-
}
|
|
2691
|
-
const model = await resolveModelContext(modelId);
|
|
2692
|
-
if (!model) {
|
|
2693
|
-
throw new ConfigError(
|
|
2694
|
-
`Unknown model "${modelId}"; ensure the model exists in the ModelRelay catalog`
|
|
2695
|
-
);
|
|
2696
|
-
}
|
|
2697
|
-
const contextWindow = normalizePositiveInt(model.contextWindow, "context_window");
|
|
2698
|
-
const modelOutputTokens = model.maxOutputTokens === void 0 ? 0 : normalizeNonNegativeInt(model.maxOutputTokens, "max_output_tokens");
|
|
2699
|
-
const effectiveReserve = reservedOutputTokens ?? modelOutputTokens;
|
|
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
|
-
);
|
|
2709
|
-
}
|
|
2710
|
-
return {
|
|
2711
|
-
maxHistoryTokens,
|
|
2712
|
-
reservedOutputTokens: effectiveReserve
|
|
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`);
|
|
2718
|
-
}
|
|
2719
|
-
return Math.floor(value);
|
|
2720
|
-
}
|
|
2721
|
-
function getModelContextCacheEntry(client) {
|
|
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;
|
|
2727
|
-
}
|
|
2728
|
-
async function populateModelContextCache(client, entry) {
|
|
2729
|
-
if (!entry.listPromise) {
|
|
2730
|
-
entry.listPromise = (async () => {
|
|
2731
|
-
const response = await client.http.json("/models");
|
|
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;
|
|
2743
|
-
}
|
|
2744
|
-
|
|
2745
|
-
// src/sessions/types.ts
|
|
2746
|
-
function asSessionId(value) {
|
|
2747
|
-
return value;
|
|
2748
|
-
}
|
|
2749
|
-
function generateSessionId() {
|
|
2750
|
-
return crypto.randomUUID();
|
|
2983
|
+
function generateSessionId() {
|
|
2984
|
+
return crypto.randomUUID();
|
|
2751
2985
|
}
|
|
2752
2986
|
|
|
2753
2987
|
// src/sessions/stores/memory_store.ts
|
|
2754
|
-
var
|
|
2988
|
+
var MemoryConversationStore = class {
|
|
2755
2989
|
constructor() {
|
|
2756
2990
|
this.sessions = /* @__PURE__ */ new Map();
|
|
2757
2991
|
}
|
|
@@ -2780,33 +3014,300 @@ var MemorySessionStore = class {
|
|
|
2780
3014
|
return this.sessions.size;
|
|
2781
3015
|
}
|
|
2782
3016
|
};
|
|
2783
|
-
function
|
|
2784
|
-
return new
|
|
3017
|
+
function createMemoryConversationStore() {
|
|
3018
|
+
return new MemoryConversationStore();
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
// src/sessions/stores/serialization.ts
|
|
3022
|
+
function serializeConversationState(state) {
|
|
3023
|
+
return {
|
|
3024
|
+
...state,
|
|
3025
|
+
messages: state.messages.map((message) => ({
|
|
3026
|
+
...message,
|
|
3027
|
+
createdAt: message.createdAt.toISOString()
|
|
3028
|
+
}))
|
|
3029
|
+
};
|
|
3030
|
+
}
|
|
3031
|
+
function deserializeConversationState(state) {
|
|
3032
|
+
return {
|
|
3033
|
+
...state,
|
|
3034
|
+
messages: state.messages.map((message) => ({
|
|
3035
|
+
...message,
|
|
3036
|
+
createdAt: new Date(message.createdAt)
|
|
3037
|
+
}))
|
|
3038
|
+
};
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
// src/sessions/stores/file_store.ts
|
|
3042
|
+
var DEFAULT_SESSION_DIR = ".modelrelay/sessions";
|
|
3043
|
+
async function loadNodeDeps() {
|
|
3044
|
+
try {
|
|
3045
|
+
const fs = await import("fs/promises");
|
|
3046
|
+
const path = await import("path");
|
|
3047
|
+
const os = await import("os");
|
|
3048
|
+
return { fs, path, os };
|
|
3049
|
+
} catch (err) {
|
|
3050
|
+
throw new ConfigError("file persistence requires a Node.js-compatible runtime");
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
var FileConversationStore = class {
|
|
3054
|
+
constructor(storagePath) {
|
|
3055
|
+
this.storagePath = storagePath;
|
|
3056
|
+
}
|
|
3057
|
+
async load(id) {
|
|
3058
|
+
const { fs, path, os } = await loadNodeDeps();
|
|
3059
|
+
const filePath = await this.resolveSessionPath(id, path, os);
|
|
3060
|
+
try {
|
|
3061
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
3062
|
+
const parsed = JSON.parse(raw);
|
|
3063
|
+
return deserializeConversationState(parsed);
|
|
3064
|
+
} catch (err) {
|
|
3065
|
+
if (isNotFoundError(err)) return null;
|
|
3066
|
+
throw err;
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
async save(state) {
|
|
3070
|
+
const { fs, path, os } = await loadNodeDeps();
|
|
3071
|
+
const dirPath = await this.resolveSessionDir(path, os);
|
|
3072
|
+
await fs.mkdir(dirPath, { recursive: true, mode: 448 });
|
|
3073
|
+
const filePath = path.join(dirPath, `${state.id}.json`);
|
|
3074
|
+
const payload = JSON.stringify(serializeConversationState(state), null, 2);
|
|
3075
|
+
await fs.writeFile(filePath, payload, { mode: 384 });
|
|
3076
|
+
}
|
|
3077
|
+
async delete(id) {
|
|
3078
|
+
const { fs, path, os } = await loadNodeDeps();
|
|
3079
|
+
const filePath = await this.resolveSessionPath(id, path, os);
|
|
3080
|
+
try {
|
|
3081
|
+
await fs.unlink(filePath);
|
|
3082
|
+
} catch (err) {
|
|
3083
|
+
if (isNotFoundError(err)) return;
|
|
3084
|
+
throw err;
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
async list() {
|
|
3088
|
+
const { fs, path, os } = await loadNodeDeps();
|
|
3089
|
+
const dirPath = await this.resolveSessionDir(path, os);
|
|
3090
|
+
try {
|
|
3091
|
+
const entries = await fs.readdir(dirPath);
|
|
3092
|
+
return entries.filter((entry) => path.extname(entry) === ".json").map((entry) => entry.replace(/\.json$/, ""));
|
|
3093
|
+
} catch (err) {
|
|
3094
|
+
if (isNotFoundError(err)) return [];
|
|
3095
|
+
throw err;
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
async close() {
|
|
3099
|
+
}
|
|
3100
|
+
async resolveSessionPath(id, path, os) {
|
|
3101
|
+
const dirPath = await this.resolveSessionDir(path, os);
|
|
3102
|
+
return path.join(dirPath, `${id}.json`);
|
|
3103
|
+
}
|
|
3104
|
+
async resolveSessionDir(path, os) {
|
|
3105
|
+
if (this.storagePath && this.storagePath.trim()) {
|
|
3106
|
+
return this.storagePath;
|
|
3107
|
+
}
|
|
3108
|
+
return path.join(os.homedir(), DEFAULT_SESSION_DIR);
|
|
3109
|
+
}
|
|
3110
|
+
};
|
|
3111
|
+
function createFileConversationStore(storagePath) {
|
|
3112
|
+
return new FileConversationStore(storagePath);
|
|
3113
|
+
}
|
|
3114
|
+
function isNotFoundError(err) {
|
|
3115
|
+
return Boolean(
|
|
3116
|
+
err && typeof err === "object" && "code" in err && err.code === "ENOENT"
|
|
3117
|
+
);
|
|
3118
|
+
}
|
|
3119
|
+
|
|
3120
|
+
// src/sessions/stores/sqlite_store.ts
|
|
3121
|
+
var DEFAULT_DB_PATH = ".modelrelay/sessions.sqlite";
|
|
3122
|
+
async function loadNodeDeps2() {
|
|
3123
|
+
try {
|
|
3124
|
+
const path = await import("path");
|
|
3125
|
+
const os = await import("os");
|
|
3126
|
+
return { path, os };
|
|
3127
|
+
} catch (err) {
|
|
3128
|
+
throw new ConfigError("sqlite persistence requires a Node.js-compatible runtime");
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3131
|
+
async function loadSqlite() {
|
|
3132
|
+
try {
|
|
3133
|
+
const mod = await import("better-sqlite3");
|
|
3134
|
+
const Database = mod.default ?? mod;
|
|
3135
|
+
if (typeof Database !== "function") {
|
|
3136
|
+
throw new Error("better-sqlite3 export missing");
|
|
3137
|
+
}
|
|
3138
|
+
return Database;
|
|
3139
|
+
} catch (err) {
|
|
3140
|
+
throw new ConfigError(
|
|
3141
|
+
"sqlite persistence requires the optional 'better-sqlite3' dependency"
|
|
3142
|
+
);
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
var SqliteConversationStore = class {
|
|
3146
|
+
constructor(storagePath) {
|
|
3147
|
+
this.storagePath = storagePath;
|
|
3148
|
+
}
|
|
3149
|
+
async load(id) {
|
|
3150
|
+
const statements = await this.getStatements();
|
|
3151
|
+
const row = statements.get.get({ id });
|
|
3152
|
+
if (!row) return null;
|
|
3153
|
+
const parsed = {
|
|
3154
|
+
id: row.id,
|
|
3155
|
+
messages: JSON.parse(row.messages),
|
|
3156
|
+
artifacts: JSON.parse(row.artifacts),
|
|
3157
|
+
metadata: JSON.parse(row.metadata),
|
|
3158
|
+
createdAt: row.createdAt,
|
|
3159
|
+
updatedAt: row.updatedAt
|
|
3160
|
+
};
|
|
3161
|
+
return deserializeConversationState(parsed);
|
|
3162
|
+
}
|
|
3163
|
+
async save(state) {
|
|
3164
|
+
const statements = await this.getStatements();
|
|
3165
|
+
const payload = serializeConversationState(state);
|
|
3166
|
+
statements.save.run({
|
|
3167
|
+
id: payload.id,
|
|
3168
|
+
messages: JSON.stringify(payload.messages),
|
|
3169
|
+
artifacts: JSON.stringify(payload.artifacts ?? {}),
|
|
3170
|
+
metadata: JSON.stringify(payload.metadata ?? {}),
|
|
3171
|
+
created_at: payload.createdAt,
|
|
3172
|
+
updated_at: payload.updatedAt
|
|
3173
|
+
});
|
|
3174
|
+
}
|
|
3175
|
+
async delete(id) {
|
|
3176
|
+
const statements = await this.getStatements();
|
|
3177
|
+
statements.delete.run({ id });
|
|
3178
|
+
}
|
|
3179
|
+
async list() {
|
|
3180
|
+
const statements = await this.getStatements();
|
|
3181
|
+
const rows = statements.list.all();
|
|
3182
|
+
return rows.map((row) => row.id);
|
|
3183
|
+
}
|
|
3184
|
+
async close() {
|
|
3185
|
+
if (this.db) {
|
|
3186
|
+
this.db.close();
|
|
3187
|
+
this.db = void 0;
|
|
3188
|
+
this.statements = void 0;
|
|
3189
|
+
this.initPromise = void 0;
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
async ensureInitialized() {
|
|
3193
|
+
if (this.db) return;
|
|
3194
|
+
if (!this.initPromise) {
|
|
3195
|
+
this.initPromise = this.initialize();
|
|
3196
|
+
}
|
|
3197
|
+
await this.initPromise;
|
|
3198
|
+
}
|
|
3199
|
+
async getStatements() {
|
|
3200
|
+
await this.ensureInitialized();
|
|
3201
|
+
if (!this.statements) {
|
|
3202
|
+
throw new Error("Database initialization failed");
|
|
3203
|
+
}
|
|
3204
|
+
return this.statements;
|
|
3205
|
+
}
|
|
3206
|
+
async initialize() {
|
|
3207
|
+
const { path, os } = await loadNodeDeps2();
|
|
3208
|
+
const Database = await loadSqlite();
|
|
3209
|
+
const dbPath = this.resolveDbPath(path, os);
|
|
3210
|
+
this.db = new Database(dbPath);
|
|
3211
|
+
this.db.exec(`
|
|
3212
|
+
CREATE TABLE IF NOT EXISTS conversations (
|
|
3213
|
+
id TEXT PRIMARY KEY,
|
|
3214
|
+
messages TEXT NOT NULL,
|
|
3215
|
+
artifacts TEXT NOT NULL,
|
|
3216
|
+
metadata TEXT NOT NULL,
|
|
3217
|
+
created_at TEXT NOT NULL,
|
|
3218
|
+
updated_at TEXT NOT NULL
|
|
3219
|
+
)
|
|
3220
|
+
`);
|
|
3221
|
+
this.statements = {
|
|
3222
|
+
get: this.db.prepare(
|
|
3223
|
+
"SELECT id, messages, artifacts, metadata, created_at as createdAt, updated_at as updatedAt FROM conversations WHERE id = @id"
|
|
3224
|
+
),
|
|
3225
|
+
save: this.db.prepare(
|
|
3226
|
+
"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"
|
|
3227
|
+
),
|
|
3228
|
+
delete: this.db.prepare("DELETE FROM conversations WHERE id = @id"),
|
|
3229
|
+
list: this.db.prepare("SELECT id FROM conversations ORDER BY id")
|
|
3230
|
+
};
|
|
3231
|
+
}
|
|
3232
|
+
resolveDbPath(path, os) {
|
|
3233
|
+
if (this.storagePath && this.storagePath.trim()) {
|
|
3234
|
+
return this.storagePath;
|
|
3235
|
+
}
|
|
3236
|
+
return path.join(os.homedir(), DEFAULT_DB_PATH);
|
|
3237
|
+
}
|
|
3238
|
+
};
|
|
3239
|
+
function createSqliteConversationStore(storagePath) {
|
|
3240
|
+
return new SqliteConversationStore(storagePath);
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3243
|
+
// src/sessions/utils.ts
|
|
3244
|
+
function messagesToInput(messages) {
|
|
3245
|
+
return messages.map((m) => ({
|
|
3246
|
+
type: m.type,
|
|
3247
|
+
role: m.role,
|
|
3248
|
+
content: m.content,
|
|
3249
|
+
toolCalls: m.toolCalls,
|
|
3250
|
+
toolCallId: m.toolCallId
|
|
3251
|
+
}));
|
|
3252
|
+
}
|
|
3253
|
+
function mergeTools(defaults, overrides) {
|
|
3254
|
+
if (!defaults && !overrides) return void 0;
|
|
3255
|
+
if (!defaults) return overrides;
|
|
3256
|
+
if (!overrides) return defaults;
|
|
3257
|
+
const merged = /* @__PURE__ */ new Map();
|
|
3258
|
+
for (const tool of defaults) {
|
|
3259
|
+
if (tool.type === "function" && tool.function) {
|
|
3260
|
+
merged.set(tool.function.name, tool);
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
for (const tool of overrides) {
|
|
3264
|
+
if (tool.type === "function" && tool.function) {
|
|
3265
|
+
merged.set(tool.function.name, tool);
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
return Array.from(merged.values());
|
|
3269
|
+
}
|
|
3270
|
+
function emptyUsage() {
|
|
3271
|
+
return {
|
|
3272
|
+
inputTokens: 0,
|
|
3273
|
+
outputTokens: 0,
|
|
3274
|
+
totalTokens: 0,
|
|
3275
|
+
llmCalls: 0,
|
|
3276
|
+
toolCalls: 0
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
3279
|
+
function createRequestBuilder(config) {
|
|
3280
|
+
return (builder) => {
|
|
3281
|
+
let next = builder;
|
|
3282
|
+
if (config.model) {
|
|
3283
|
+
next = next.model(config.model);
|
|
3284
|
+
}
|
|
3285
|
+
if (config.provider) {
|
|
3286
|
+
next = next.provider(config.provider);
|
|
3287
|
+
}
|
|
3288
|
+
if (config.customerId) {
|
|
3289
|
+
next = next.customerId(config.customerId);
|
|
3290
|
+
}
|
|
3291
|
+
return next;
|
|
3292
|
+
};
|
|
2785
3293
|
}
|
|
2786
3294
|
|
|
2787
3295
|
// src/sessions/local_session.ts
|
|
3296
|
+
var DEFAULT_MAX_TURNS2 = 100;
|
|
2788
3297
|
var LocalSession = class _LocalSession {
|
|
2789
3298
|
constructor(client, store, options, existingState) {
|
|
2790
3299
|
this.type = "local";
|
|
2791
3300
|
this.messages = [];
|
|
2792
3301
|
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
3302
|
this.client = client;
|
|
2803
3303
|
this.store = store;
|
|
2804
3304
|
this.toolRegistry = options.toolRegistry;
|
|
3305
|
+
this.contextManager = options.contextManager;
|
|
2805
3306
|
this.defaultModel = options.defaultModel;
|
|
2806
3307
|
this.defaultProvider = options.defaultProvider;
|
|
2807
3308
|
this.defaultTools = options.defaultTools;
|
|
3309
|
+
this.systemPrompt = options.systemPrompt;
|
|
2808
3310
|
this.metadata = options.metadata || {};
|
|
2809
|
-
this.resolveModelContext = createModelContextResolver(client);
|
|
2810
3311
|
if (existingState) {
|
|
2811
3312
|
this.id = existingState.id;
|
|
2812
3313
|
this.messages = existingState.messages.map((m) => ({
|
|
@@ -2814,7 +3315,6 @@ var LocalSession = class _LocalSession {
|
|
|
2814
3315
|
createdAt: new Date(m.createdAt)
|
|
2815
3316
|
}));
|
|
2816
3317
|
this.artifacts = new Map(Object.entries(existingState.artifacts));
|
|
2817
|
-
this.nextSeq = this.messages.length + 1;
|
|
2818
3318
|
this.createdAt = new Date(existingState.createdAt);
|
|
2819
3319
|
this.updatedAt = new Date(existingState.updatedAt);
|
|
2820
3320
|
} else {
|
|
@@ -2831,7 +3331,11 @@ var LocalSession = class _LocalSession {
|
|
|
2831
3331
|
* @returns A new LocalSession instance
|
|
2832
3332
|
*/
|
|
2833
3333
|
static create(client, options = {}) {
|
|
2834
|
-
const store = createStore(
|
|
3334
|
+
const store = createStore(
|
|
3335
|
+
options.conversationStore,
|
|
3336
|
+
options.persistence || "memory",
|
|
3337
|
+
options.storagePath
|
|
3338
|
+
);
|
|
2835
3339
|
return new _LocalSession(client, store, options);
|
|
2836
3340
|
}
|
|
2837
3341
|
/**
|
|
@@ -2844,7 +3348,11 @@ var LocalSession = class _LocalSession {
|
|
|
2844
3348
|
*/
|
|
2845
3349
|
static async resume(client, sessionId, options = {}) {
|
|
2846
3350
|
const id = typeof sessionId === "string" ? asSessionId(sessionId) : sessionId;
|
|
2847
|
-
const store = createStore(
|
|
3351
|
+
const store = createStore(
|
|
3352
|
+
options.conversationStore,
|
|
3353
|
+
options.persistence || "memory",
|
|
3354
|
+
options.storagePath
|
|
3355
|
+
);
|
|
2848
3356
|
const state = await store.load(id);
|
|
2849
3357
|
if (!state) {
|
|
2850
3358
|
await store.close();
|
|
@@ -2856,74 +3364,137 @@ var LocalSession = class _LocalSession {
|
|
|
2856
3364
|
return this.messages;
|
|
2857
3365
|
}
|
|
2858
3366
|
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;
|
|
3367
|
+
this.pendingLoop = void 0;
|
|
3368
|
+
this.messages.push(buildMessage(createUserMessage(prompt), this.messages.length + 1));
|
|
3369
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
3370
|
+
const baseInput = messagesToInput(this.messages);
|
|
3371
|
+
const contextOptions = this.buildContextOptions(options);
|
|
2875
3372
|
try {
|
|
2876
|
-
const
|
|
3373
|
+
const prepared = await this.prepareInput(baseInput, contextOptions);
|
|
2877
3374
|
const tools = mergeTools(this.defaultTools, options.tools);
|
|
2878
|
-
const
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
3375
|
+
const modelId = options.model ?? this.defaultModel;
|
|
3376
|
+
const providerId = options.provider ?? this.defaultProvider;
|
|
3377
|
+
const requestOptions = options.signal ? { signal: options.signal } : {};
|
|
3378
|
+
const outcome = await runToolLoop({
|
|
3379
|
+
client: this.client.responses,
|
|
3380
|
+
input: prepared,
|
|
3381
|
+
tools,
|
|
3382
|
+
registry: this.toolRegistry,
|
|
3383
|
+
maxTurns: options.maxTurns ?? DEFAULT_MAX_TURNS2,
|
|
3384
|
+
requestOptions,
|
|
3385
|
+
buildRequest: createRequestBuilder({
|
|
3386
|
+
model: modelId,
|
|
3387
|
+
provider: providerId,
|
|
3388
|
+
customerId: options.customerId
|
|
3389
|
+
})
|
|
3390
|
+
});
|
|
3391
|
+
const cleanInput = stripSystemPrompt(outcome.input, this.systemPrompt);
|
|
3392
|
+
this.replaceHistory(cleanInput);
|
|
3393
|
+
await this.persist();
|
|
3394
|
+
const usage = outcome.usage;
|
|
3395
|
+
if (outcome.status === "waiting_for_tools") {
|
|
3396
|
+
const pendingRequestOptions = { ...requestOptions };
|
|
3397
|
+
delete pendingRequestOptions.signal;
|
|
3398
|
+
this.pendingLoop = {
|
|
3399
|
+
input: cleanInput,
|
|
3400
|
+
usage,
|
|
3401
|
+
remainingTurns: remainingTurns(
|
|
3402
|
+
options.maxTurns ?? DEFAULT_MAX_TURNS2,
|
|
3403
|
+
outcome.turnsUsed
|
|
3404
|
+
),
|
|
3405
|
+
config: {
|
|
3406
|
+
model: modelId,
|
|
3407
|
+
provider: providerId,
|
|
2887
3408
|
tools,
|
|
2888
|
-
|
|
3409
|
+
customerId: options.customerId,
|
|
3410
|
+
requestOptions: pendingRequestOptions,
|
|
3411
|
+
contextOptions
|
|
2889
3412
|
}
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
3413
|
+
};
|
|
3414
|
+
return {
|
|
3415
|
+
status: "waiting_for_tools",
|
|
3416
|
+
pendingTools: mapPendingToolCalls(outcome.pendingToolCalls),
|
|
3417
|
+
response: outcome.response,
|
|
3418
|
+
usage
|
|
3419
|
+
};
|
|
3420
|
+
}
|
|
3421
|
+
return {
|
|
3422
|
+
status: "complete",
|
|
3423
|
+
output: outcome.output,
|
|
3424
|
+
response: outcome.response,
|
|
3425
|
+
usage
|
|
3426
|
+
};
|
|
2898
3427
|
} catch (err) {
|
|
2899
3428
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
2900
3429
|
return {
|
|
2901
3430
|
status: "error",
|
|
2902
3431
|
error: error.message,
|
|
2903
|
-
|
|
2904
|
-
usage:
|
|
2905
|
-
events: this.currentEvents
|
|
3432
|
+
cause: error,
|
|
3433
|
+
usage: emptyUsage()
|
|
2906
3434
|
};
|
|
2907
3435
|
}
|
|
2908
3436
|
}
|
|
2909
3437
|
async submitToolResults(results) {
|
|
2910
|
-
if (!this.
|
|
3438
|
+
if (!this.pendingLoop) {
|
|
2911
3439
|
throw new Error("No pending tool calls to submit results for");
|
|
2912
3440
|
}
|
|
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
|
-
}))
|
|
3441
|
+
const pending = this.pendingLoop;
|
|
3442
|
+
this.pendingLoop = void 0;
|
|
3443
|
+
if (pending.remainingTurns <= 0) {
|
|
3444
|
+
throw new AgentMaxTurnsError(0);
|
|
3445
|
+
}
|
|
3446
|
+
const resultItems = results.map((result) => {
|
|
3447
|
+
const content = result.error ? `Error: ${result.error}` : typeof result.result === "string" ? result.result : JSON.stringify(result.result);
|
|
3448
|
+
return toolResultMessage(result.toolCallId, content);
|
|
2924
3449
|
});
|
|
2925
|
-
|
|
2926
|
-
|
|
3450
|
+
const baseInput = [...pending.input, ...resultItems];
|
|
3451
|
+
try {
|
|
3452
|
+
const prepared = await this.prepareInput(baseInput, pending.config.contextOptions);
|
|
3453
|
+
const outcome = await runToolLoop({
|
|
3454
|
+
client: this.client.responses,
|
|
3455
|
+
input: prepared,
|
|
3456
|
+
tools: pending.config.tools,
|
|
3457
|
+
registry: this.toolRegistry,
|
|
3458
|
+
maxTurns: pending.remainingTurns,
|
|
3459
|
+
requestOptions: pending.config.requestOptions,
|
|
3460
|
+
buildRequest: createRequestBuilder(pending.config)
|
|
3461
|
+
});
|
|
3462
|
+
const cleanInput = stripSystemPrompt(outcome.input, this.systemPrompt);
|
|
3463
|
+
this.replaceHistory(cleanInput);
|
|
3464
|
+
await this.persist();
|
|
3465
|
+
const usage = mergeUsage(pending.usage, outcome.usage);
|
|
3466
|
+
if (outcome.status === "waiting_for_tools") {
|
|
3467
|
+
this.pendingLoop = {
|
|
3468
|
+
input: cleanInput,
|
|
3469
|
+
usage,
|
|
3470
|
+
remainingTurns: remainingTurns(
|
|
3471
|
+
pending.remainingTurns,
|
|
3472
|
+
outcome.turnsUsed
|
|
3473
|
+
),
|
|
3474
|
+
config: pending.config
|
|
3475
|
+
};
|
|
3476
|
+
return {
|
|
3477
|
+
status: "waiting_for_tools",
|
|
3478
|
+
pendingTools: mapPendingToolCalls(outcome.pendingToolCalls),
|
|
3479
|
+
response: outcome.response,
|
|
3480
|
+
usage
|
|
3481
|
+
};
|
|
3482
|
+
}
|
|
3483
|
+
return {
|
|
3484
|
+
status: "complete",
|
|
3485
|
+
output: outcome.output,
|
|
3486
|
+
response: outcome.response,
|
|
3487
|
+
usage
|
|
3488
|
+
};
|
|
3489
|
+
} catch (err) {
|
|
3490
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
3491
|
+
return {
|
|
3492
|
+
status: "error",
|
|
3493
|
+
error: error.message,
|
|
3494
|
+
cause: error,
|
|
3495
|
+
usage: pending.usage
|
|
3496
|
+
};
|
|
3497
|
+
}
|
|
2927
3498
|
}
|
|
2928
3499
|
getArtifacts() {
|
|
2929
3500
|
return new Map(this.artifacts);
|
|
@@ -2934,28 +3505,6 @@ var LocalSession = class _LocalSession {
|
|
|
2934
3505
|
}
|
|
2935
3506
|
/**
|
|
2936
3507
|
* 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
3508
|
*/
|
|
2960
3509
|
async syncTo(remoteSession, options = {}) {
|
|
2961
3510
|
const { onProgress, signal } = options;
|
|
@@ -2997,150 +3546,35 @@ var LocalSession = class _LocalSession {
|
|
|
2997
3546
|
// ============================================================================
|
|
2998
3547
|
// Private Methods
|
|
2999
3548
|
// ============================================================================
|
|
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
|
-
}
|
|
3549
|
+
buildContextOptions(options) {
|
|
3550
|
+
if (!this.contextManager) return null;
|
|
3551
|
+
if (options.contextManagement === "none") return null;
|
|
3109
3552
|
return {
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3553
|
+
model: options.model ?? this.defaultModel,
|
|
3554
|
+
strategy: options.contextManagement,
|
|
3555
|
+
maxHistoryTokens: options.maxHistoryTokens,
|
|
3556
|
+
reserveOutputTokens: options.reserveOutputTokens,
|
|
3557
|
+
onTruncate: options.onContextTruncate
|
|
3115
3558
|
};
|
|
3116
3559
|
}
|
|
3117
|
-
async
|
|
3118
|
-
|
|
3119
|
-
|
|
3560
|
+
async prepareInput(input, contextOptions) {
|
|
3561
|
+
let prepared = input;
|
|
3562
|
+
if (this.systemPrompt) {
|
|
3563
|
+
prepared = [createSystemMessage(this.systemPrompt), ...prepared];
|
|
3120
3564
|
}
|
|
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
|
-
}
|
|
3565
|
+
if (!this.contextManager || !contextOptions) {
|
|
3566
|
+
return prepared;
|
|
3142
3567
|
}
|
|
3143
|
-
return
|
|
3568
|
+
return this.contextManager.prepare(prepared, contextOptions);
|
|
3569
|
+
}
|
|
3570
|
+
replaceHistory(input) {
|
|
3571
|
+
const now = /* @__PURE__ */ new Date();
|
|
3572
|
+
this.messages = input.map((item, idx) => ({
|
|
3573
|
+
...item,
|
|
3574
|
+
seq: idx + 1,
|
|
3575
|
+
createdAt: now
|
|
3576
|
+
}));
|
|
3577
|
+
this.updatedAt = now;
|
|
3144
3578
|
}
|
|
3145
3579
|
async persist() {
|
|
3146
3580
|
const state = {
|
|
@@ -3157,143 +3591,357 @@ var LocalSession = class _LocalSession {
|
|
|
3157
3591
|
await this.store.save(state);
|
|
3158
3592
|
}
|
|
3159
3593
|
};
|
|
3160
|
-
function createStore(persistence, storagePath) {
|
|
3594
|
+
function createStore(custom, persistence, storagePath) {
|
|
3595
|
+
if (custom) {
|
|
3596
|
+
return custom;
|
|
3597
|
+
}
|
|
3161
3598
|
switch (persistence) {
|
|
3162
3599
|
case "memory":
|
|
3163
|
-
return
|
|
3600
|
+
return createMemoryConversationStore();
|
|
3164
3601
|
case "file":
|
|
3165
|
-
|
|
3602
|
+
return createFileConversationStore(storagePath);
|
|
3166
3603
|
case "sqlite":
|
|
3167
|
-
|
|
3604
|
+
return createSqliteConversationStore(storagePath);
|
|
3168
3605
|
default:
|
|
3169
3606
|
throw new Error(`Unknown persistence mode: ${persistence}`);
|
|
3170
3607
|
}
|
|
3171
3608
|
}
|
|
3172
|
-
function
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3609
|
+
function buildMessage(item, seq) {
|
|
3610
|
+
return {
|
|
3611
|
+
...item,
|
|
3612
|
+
seq,
|
|
3613
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
3614
|
+
};
|
|
3615
|
+
}
|
|
3616
|
+
function stripSystemPrompt(input, systemPrompt) {
|
|
3617
|
+
if (!systemPrompt || input.length === 0) {
|
|
3618
|
+
return input;
|
|
3181
3619
|
}
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
}
|
|
3620
|
+
const [first, ...rest] = input;
|
|
3621
|
+
if (first.role === "system" && first.content?.length === 1 && first.content[0].type === "text" && first.content[0].text === systemPrompt) {
|
|
3622
|
+
return rest;
|
|
3186
3623
|
}
|
|
3187
|
-
return
|
|
3624
|
+
return input;
|
|
3188
3625
|
}
|
|
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
|
-
}
|
|
3626
|
+
function mapPendingToolCalls(calls) {
|
|
3627
|
+
return calls.map((call) => {
|
|
3628
|
+
if (!call.function?.name) {
|
|
3629
|
+
throw new Error(`Tool call ${call.id} missing function name`);
|
|
3220
3630
|
}
|
|
3631
|
+
return {
|
|
3632
|
+
toolCallId: call.id,
|
|
3633
|
+
name: call.function.name,
|
|
3634
|
+
arguments: call.function.arguments ?? "{}"
|
|
3635
|
+
};
|
|
3636
|
+
});
|
|
3637
|
+
}
|
|
3638
|
+
function remainingTurns(maxTurns, turnsUsed) {
|
|
3639
|
+
if (maxTurns === Number.MAX_SAFE_INTEGER) {
|
|
3640
|
+
return maxTurns;
|
|
3221
3641
|
}
|
|
3222
|
-
return
|
|
3642
|
+
return Math.max(0, maxTurns - turnsUsed);
|
|
3643
|
+
}
|
|
3644
|
+
function mergeUsage(base, add) {
|
|
3645
|
+
return {
|
|
3646
|
+
inputTokens: base.inputTokens + add.inputTokens,
|
|
3647
|
+
outputTokens: base.outputTokens + add.outputTokens,
|
|
3648
|
+
totalTokens: base.totalTokens + add.totalTokens,
|
|
3649
|
+
llmCalls: base.llmCalls + add.llmCalls,
|
|
3650
|
+
toolCalls: base.toolCalls + add.toolCalls
|
|
3651
|
+
};
|
|
3223
3652
|
}
|
|
3224
3653
|
function createLocalSession(client, options = {}) {
|
|
3225
3654
|
return LocalSession.create(client, options);
|
|
3226
3655
|
}
|
|
3227
3656
|
|
|
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;
|
|
3657
|
+
// src/context_manager.ts
|
|
3658
|
+
var DEFAULT_CONTEXT_BUFFER_TOKENS = 256;
|
|
3659
|
+
var CONTEXT_BUFFER_RATIO = 0.02;
|
|
3660
|
+
var MESSAGE_OVERHEAD_TOKENS = 6;
|
|
3661
|
+
var TOOL_CALL_OVERHEAD_TOKENS = 4;
|
|
3662
|
+
var CHARS_PER_TOKEN = 4;
|
|
3663
|
+
var IMAGE_TOKENS_LOW_DETAIL = 85;
|
|
3664
|
+
var IMAGE_TOKENS_HIGH_DETAIL = 1e3;
|
|
3665
|
+
var modelContextCache = /* @__PURE__ */ new WeakMap();
|
|
3666
|
+
function createModelContextResolver(client) {
|
|
3667
|
+
return async (modelId) => {
|
|
3668
|
+
const entry = getModelContextCacheEntry(client);
|
|
3669
|
+
const key = String(modelId);
|
|
3670
|
+
const cached = entry.byId.get(key);
|
|
3671
|
+
if (cached !== void 0) {
|
|
3672
|
+
return cached;
|
|
3673
|
+
}
|
|
3674
|
+
await populateModelContextCache(client, entry);
|
|
3675
|
+
const resolved = entry.byId.get(key);
|
|
3676
|
+
if (resolved === void 0) {
|
|
3677
|
+
throw new ConfigError(
|
|
3678
|
+
`Unknown model "${key}"; ensure the model exists in the ModelRelay catalog`
|
|
3679
|
+
);
|
|
3266
3680
|
}
|
|
3681
|
+
return resolved;
|
|
3682
|
+
};
|
|
3683
|
+
}
|
|
3684
|
+
var ContextManager = class {
|
|
3685
|
+
constructor(resolveModelContext, defaults = {}) {
|
|
3686
|
+
this.resolveModelContext = resolveModelContext;
|
|
3687
|
+
this.defaults = defaults;
|
|
3688
|
+
}
|
|
3689
|
+
async prepare(input, options = {}) {
|
|
3690
|
+
const merged = {
|
|
3691
|
+
...this.defaults,
|
|
3692
|
+
...options
|
|
3693
|
+
};
|
|
3694
|
+
return prepareInputWithContext(input, merged, this.resolveModelContext);
|
|
3267
3695
|
}
|
|
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);
|
|
3696
|
+
};
|
|
3697
|
+
async function prepareInputWithContext(input, options, resolveModelContext) {
|
|
3698
|
+
const strategy = options.strategy ?? "truncate";
|
|
3699
|
+
if (strategy === "summarize") {
|
|
3700
|
+
throw new ConfigError("context management 'summarize' is not implemented yet");
|
|
3286
3701
|
}
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3702
|
+
if (strategy !== "truncate") {
|
|
3703
|
+
throw new ConfigError(`Unknown context management strategy: ${strategy}`);
|
|
3704
|
+
}
|
|
3705
|
+
const budget = await resolveHistoryBudget(
|
|
3706
|
+
options.model,
|
|
3707
|
+
options,
|
|
3708
|
+
resolveModelContext
|
|
3709
|
+
);
|
|
3710
|
+
const truncated = truncateInputByTokens(input, budget.maxHistoryTokens);
|
|
3711
|
+
if (options.onTruncate && truncated.length < input.length) {
|
|
3712
|
+
if (!options.model) {
|
|
3713
|
+
throw new ConfigError(
|
|
3714
|
+
"model is required for context management; set options.model"
|
|
3715
|
+
);
|
|
3716
|
+
}
|
|
3717
|
+
const info = {
|
|
3718
|
+
model: options.model,
|
|
3719
|
+
originalMessages: input.length,
|
|
3720
|
+
keptMessages: truncated.length,
|
|
3721
|
+
maxHistoryTokens: budget.maxHistoryTokens,
|
|
3722
|
+
reservedOutputTokens: budget.reservedOutputTokens
|
|
3723
|
+
};
|
|
3724
|
+
options.onTruncate(info);
|
|
3725
|
+
}
|
|
3726
|
+
return truncated;
|
|
3727
|
+
}
|
|
3728
|
+
function truncateInputByTokens(input, maxHistoryTokens) {
|
|
3729
|
+
const maxTokens = normalizePositiveInt(maxHistoryTokens, "maxHistoryTokens");
|
|
3730
|
+
if (input.length === 0) return [];
|
|
3731
|
+
const tokensByIndex = input.map((msg) => estimateTokensForMessage(msg));
|
|
3732
|
+
const systemIndices = input.map((msg, idx) => msg.role === "system" ? idx : -1).filter((idx) => idx >= 0);
|
|
3733
|
+
let selectedSystem = [...systemIndices];
|
|
3734
|
+
let systemTokens = sumTokens(tokensByIndex, selectedSystem);
|
|
3735
|
+
while (systemTokens > maxTokens && selectedSystem.length > 1) {
|
|
3736
|
+
selectedSystem.shift();
|
|
3737
|
+
systemTokens = sumTokens(tokensByIndex, selectedSystem);
|
|
3738
|
+
}
|
|
3739
|
+
if (systemTokens > maxTokens) {
|
|
3740
|
+
throw new ConfigError(
|
|
3741
|
+
"maxHistoryTokens is too small to fit the latest system message"
|
|
3742
|
+
);
|
|
3743
|
+
}
|
|
3744
|
+
const selected = new Set(selectedSystem);
|
|
3745
|
+
let remaining = maxTokens - systemTokens;
|
|
3746
|
+
for (let i = input.length - 1; i >= 0; i -= 1) {
|
|
3747
|
+
if (selected.has(i)) continue;
|
|
3748
|
+
const tokens = tokensByIndex[i];
|
|
3749
|
+
if (tokens <= remaining) {
|
|
3750
|
+
selected.add(i);
|
|
3751
|
+
remaining -= tokens;
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
const result = input.filter((_, idx) => selected.has(idx));
|
|
3755
|
+
if (result.length === 0) {
|
|
3756
|
+
throw new ConfigError("No messages fit within maxHistoryTokens");
|
|
3757
|
+
}
|
|
3758
|
+
return result;
|
|
3759
|
+
}
|
|
3760
|
+
function estimateTokens(text) {
|
|
3761
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
3762
|
+
}
|
|
3763
|
+
function isImagePart(part) {
|
|
3764
|
+
if (typeof part !== "object" || part === null) return false;
|
|
3765
|
+
const p = part;
|
|
3766
|
+
return p.type === "image" || p.type === "image_url";
|
|
3767
|
+
}
|
|
3768
|
+
function estimateImageTokens(part) {
|
|
3769
|
+
const detail = part.detail ?? "auto";
|
|
3770
|
+
if (detail === "low") return IMAGE_TOKENS_LOW_DETAIL;
|
|
3771
|
+
return IMAGE_TOKENS_HIGH_DETAIL;
|
|
3772
|
+
}
|
|
3773
|
+
function estimateTokensForMessage(message) {
|
|
3774
|
+
const segments = [message.role];
|
|
3775
|
+
let imageTokens = 0;
|
|
3776
|
+
for (const part of message.content || []) {
|
|
3777
|
+
if (part.type === "text" && part.text) {
|
|
3778
|
+
segments.push(part.text);
|
|
3779
|
+
} else if (isImagePart(part)) {
|
|
3780
|
+
imageTokens += estimateImageTokens(part);
|
|
3781
|
+
}
|
|
3782
|
+
}
|
|
3783
|
+
if (message.toolCalls) {
|
|
3784
|
+
for (const call of message.toolCalls) {
|
|
3785
|
+
if (call.function?.name) segments.push(call.function.name);
|
|
3786
|
+
if (call.function?.arguments) segments.push(call.function.arguments);
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3789
|
+
if (message.toolCallId) {
|
|
3790
|
+
segments.push(message.toolCallId);
|
|
3791
|
+
}
|
|
3792
|
+
const textTokens = estimateTokens(segments.join("\n"));
|
|
3793
|
+
const toolOverhead = message.toolCalls ? message.toolCalls.length * TOOL_CALL_OVERHEAD_TOKENS : 0;
|
|
3794
|
+
return textTokens + MESSAGE_OVERHEAD_TOKENS + toolOverhead + imageTokens;
|
|
3795
|
+
}
|
|
3796
|
+
function normalizePositiveInt(value, label) {
|
|
3797
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
3798
|
+
throw new ConfigError(`${label} must be a positive number`);
|
|
3799
|
+
}
|
|
3800
|
+
return Math.floor(value);
|
|
3801
|
+
}
|
|
3802
|
+
function sumTokens(tokensByIndex, indices) {
|
|
3803
|
+
return indices.reduce((sum, idx) => sum + tokensByIndex[idx], 0);
|
|
3804
|
+
}
|
|
3805
|
+
async function resolveHistoryBudget(modelId, options, resolveModelContext) {
|
|
3806
|
+
const reservedOutputTokens = options.reserveOutputTokens === void 0 ? void 0 : normalizeNonNegativeInt(
|
|
3807
|
+
options.reserveOutputTokens,
|
|
3808
|
+
"reserveOutputTokens"
|
|
3809
|
+
);
|
|
3810
|
+
if (options.maxHistoryTokens !== void 0) {
|
|
3811
|
+
return {
|
|
3812
|
+
maxHistoryTokens: normalizePositiveInt(
|
|
3813
|
+
options.maxHistoryTokens,
|
|
3814
|
+
"maxHistoryTokens"
|
|
3815
|
+
),
|
|
3816
|
+
reservedOutputTokens
|
|
3817
|
+
};
|
|
3818
|
+
}
|
|
3819
|
+
if (!modelId) {
|
|
3820
|
+
throw new ConfigError(
|
|
3821
|
+
"model is required for context management when maxHistoryTokens is not set"
|
|
3822
|
+
);
|
|
3823
|
+
}
|
|
3824
|
+
const model = await resolveModelContext(modelId);
|
|
3825
|
+
if (!model) {
|
|
3826
|
+
throw new ConfigError(
|
|
3827
|
+
`Unknown model "${modelId}"; ensure the model exists in the ModelRelay catalog`
|
|
3828
|
+
);
|
|
3829
|
+
}
|
|
3830
|
+
const contextWindow = normalizePositiveInt(model.contextWindow, "context_window");
|
|
3831
|
+
const modelOutputTokens = model.maxOutputTokens === void 0 ? 0 : normalizeNonNegativeInt(model.maxOutputTokens, "max_output_tokens");
|
|
3832
|
+
const effectiveReserve = reservedOutputTokens ?? modelOutputTokens;
|
|
3833
|
+
const buffer = Math.max(
|
|
3834
|
+
DEFAULT_CONTEXT_BUFFER_TOKENS,
|
|
3835
|
+
Math.ceil(contextWindow * CONTEXT_BUFFER_RATIO)
|
|
3836
|
+
);
|
|
3837
|
+
const maxHistoryTokens = contextWindow - effectiveReserve - buffer;
|
|
3838
|
+
if (maxHistoryTokens <= 0) {
|
|
3839
|
+
throw new ConfigError(
|
|
3840
|
+
"model context window is too small after reserving output tokens; set maxHistoryTokens explicitly"
|
|
3841
|
+
);
|
|
3842
|
+
}
|
|
3843
|
+
return {
|
|
3844
|
+
maxHistoryTokens,
|
|
3845
|
+
reservedOutputTokens: effectiveReserve
|
|
3846
|
+
};
|
|
3847
|
+
}
|
|
3848
|
+
function normalizeNonNegativeInt(value, label) {
|
|
3849
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
3850
|
+
throw new ConfigError(`${label} must be a non-negative number`);
|
|
3851
|
+
}
|
|
3852
|
+
return Math.floor(value);
|
|
3853
|
+
}
|
|
3854
|
+
function getModelContextCacheEntry(client) {
|
|
3855
|
+
const existing = modelContextCache.get(client);
|
|
3856
|
+
if (existing) return existing;
|
|
3857
|
+
const entry = { byId: /* @__PURE__ */ new Map() };
|
|
3858
|
+
modelContextCache.set(client, entry);
|
|
3859
|
+
return entry;
|
|
3860
|
+
}
|
|
3861
|
+
async function populateModelContextCache(client, entry) {
|
|
3862
|
+
if (!entry.listPromise) {
|
|
3863
|
+
entry.listPromise = (async () => {
|
|
3864
|
+
const response = await client.http.json("/models");
|
|
3865
|
+
for (const model of response.models) {
|
|
3866
|
+
entry.byId.set(model.model_id, {
|
|
3867
|
+
contextWindow: model.context_window,
|
|
3868
|
+
maxOutputTokens: model.max_output_tokens ?? void 0
|
|
3869
|
+
});
|
|
3870
|
+
}
|
|
3871
|
+
})();
|
|
3872
|
+
}
|
|
3873
|
+
await entry.listPromise;
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
// src/sessions/remote_session.ts
|
|
3877
|
+
var RemoteSession = class _RemoteSession {
|
|
3878
|
+
constructor(client, http, sessionData, options = {}) {
|
|
3879
|
+
this.type = "remote";
|
|
3880
|
+
this.messages = [];
|
|
3881
|
+
this.artifacts = /* @__PURE__ */ new Map();
|
|
3882
|
+
this.nextSeq = 1;
|
|
3883
|
+
this.pendingMessages = [];
|
|
3884
|
+
this.currentEvents = [];
|
|
3885
|
+
this.currentUsage = {
|
|
3886
|
+
inputTokens: 0,
|
|
3887
|
+
outputTokens: 0,
|
|
3888
|
+
totalTokens: 0,
|
|
3889
|
+
llmCalls: 0,
|
|
3890
|
+
toolCalls: 0
|
|
3891
|
+
};
|
|
3892
|
+
this.client = client;
|
|
3893
|
+
this.http = http;
|
|
3894
|
+
this.id = asSessionId(sessionData.id);
|
|
3895
|
+
this.metadata = sessionData.metadata;
|
|
3896
|
+
this.customerId = sessionData.customer_id || options.customerId;
|
|
3897
|
+
this.createdAt = new Date(sessionData.created_at);
|
|
3898
|
+
this.updatedAt = new Date(sessionData.updated_at);
|
|
3899
|
+
this.toolRegistry = options.toolRegistry;
|
|
3900
|
+
this.defaultModel = options.defaultModel;
|
|
3901
|
+
this.defaultProvider = options.defaultProvider;
|
|
3902
|
+
this.defaultTools = options.defaultTools;
|
|
3903
|
+
this.resolveModelContext = createModelContextResolver(client);
|
|
3904
|
+
if ("messages" in sessionData && sessionData.messages) {
|
|
3905
|
+
this.messages = sessionData.messages.map((m) => ({
|
|
3906
|
+
type: "message",
|
|
3907
|
+
role: m.role,
|
|
3908
|
+
content: m.content,
|
|
3909
|
+
seq: m.seq,
|
|
3910
|
+
createdAt: new Date(m.created_at),
|
|
3911
|
+
runId: m.run_id ? parseRunId(m.run_id) : void 0
|
|
3912
|
+
}));
|
|
3913
|
+
this.nextSeq = this.messages.length + 1;
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
/**
|
|
3917
|
+
* Create a new remote session on the server.
|
|
3918
|
+
*
|
|
3919
|
+
* @param client - ModelRelay client
|
|
3920
|
+
* @param options - Session configuration
|
|
3921
|
+
* @returns A new RemoteSession instance
|
|
3922
|
+
*/
|
|
3923
|
+
static async create(client, options = {}) {
|
|
3924
|
+
const http = client.http;
|
|
3925
|
+
const response = await http.request("/sessions", {
|
|
3926
|
+
method: "POST",
|
|
3927
|
+
body: {
|
|
3928
|
+
customer_id: options.customerId,
|
|
3929
|
+
metadata: options.metadata || {}
|
|
3930
|
+
}
|
|
3931
|
+
});
|
|
3932
|
+
const data = await response.json();
|
|
3933
|
+
return new _RemoteSession(client, http, data, options);
|
|
3934
|
+
}
|
|
3935
|
+
/**
|
|
3936
|
+
* Get an existing remote session by ID.
|
|
3937
|
+
*
|
|
3938
|
+
* @param client - ModelRelay client
|
|
3291
3939
|
* @param sessionId - ID of the session to retrieve
|
|
3292
3940
|
* @param options - Optional configuration (toolRegistry, defaults)
|
|
3293
3941
|
* @returns The RemoteSession instance
|
|
3294
3942
|
*/
|
|
3295
3943
|
static async get(client, sessionId, options = {}) {
|
|
3296
|
-
const http =
|
|
3944
|
+
const http = client.http;
|
|
3297
3945
|
const id = typeof sessionId === "string" ? sessionId : String(sessionId);
|
|
3298
3946
|
const response = await http.request(`/sessions/${id}`, {
|
|
3299
3947
|
method: "GET"
|
|
@@ -3309,7 +3957,7 @@ var RemoteSession = class _RemoteSession {
|
|
|
3309
3957
|
* @returns Paginated list of session info
|
|
3310
3958
|
*/
|
|
3311
3959
|
static async list(client, options = {}) {
|
|
3312
|
-
const http =
|
|
3960
|
+
const http = client.http;
|
|
3313
3961
|
const params = new URLSearchParams();
|
|
3314
3962
|
if (options.limit) params.set("limit", String(options.limit));
|
|
3315
3963
|
if (options.offset) params.set("offset", String(options.offset));
|
|
@@ -3337,7 +3985,7 @@ var RemoteSession = class _RemoteSession {
|
|
|
3337
3985
|
* @param sessionId - ID of the session to delete
|
|
3338
3986
|
*/
|
|
3339
3987
|
static async delete(client, sessionId) {
|
|
3340
|
-
const http =
|
|
3988
|
+
const http = client.http;
|
|
3341
3989
|
const id = typeof sessionId === "string" ? sessionId : String(sessionId);
|
|
3342
3990
|
await http.request(`/sessions/${id}`, {
|
|
3343
3991
|
method: "DELETE"
|
|
@@ -3364,21 +4012,22 @@ var RemoteSession = class _RemoteSession {
|
|
|
3364
4012
|
this.resetRunState();
|
|
3365
4013
|
try {
|
|
3366
4014
|
const input = await this.buildInput(options);
|
|
3367
|
-
const tools =
|
|
4015
|
+
const tools = mergeTools(this.defaultTools, options.tools);
|
|
4016
|
+
const mainNodeId = parseNodeId("main");
|
|
3368
4017
|
const spec = {
|
|
3369
4018
|
kind: "workflow",
|
|
3370
4019
|
name: `session-${this.id}-turn-${this.nextSeq}`,
|
|
3371
4020
|
model: options.model || this.defaultModel,
|
|
3372
4021
|
nodes: [
|
|
3373
4022
|
{
|
|
3374
|
-
id:
|
|
4023
|
+
id: mainNodeId,
|
|
3375
4024
|
type: "llm",
|
|
3376
4025
|
input,
|
|
3377
4026
|
tools,
|
|
3378
4027
|
tool_execution: this.toolRegistry ? { mode: "client" } : void 0
|
|
3379
4028
|
}
|
|
3380
4029
|
],
|
|
3381
|
-
outputs: [{ name: "result", from:
|
|
4030
|
+
outputs: [{ name: parseOutputName("result"), from: mainNodeId }]
|
|
3382
4031
|
};
|
|
3383
4032
|
const run = await this.client.runs.create(spec, {
|
|
3384
4033
|
customerId: options.customerId || this.customerId,
|
|
@@ -3391,7 +4040,8 @@ var RemoteSession = class _RemoteSession {
|
|
|
3391
4040
|
return {
|
|
3392
4041
|
status: "error",
|
|
3393
4042
|
error: error.message,
|
|
3394
|
-
|
|
4043
|
+
cause: error,
|
|
4044
|
+
runId: this.currentRunId,
|
|
3395
4045
|
usage: { ...this.currentUsage },
|
|
3396
4046
|
events: [...this.currentEvents]
|
|
3397
4047
|
};
|
|
@@ -3470,10 +4120,25 @@ var RemoteSession = class _RemoteSession {
|
|
|
3470
4120
|
return message;
|
|
3471
4121
|
}
|
|
3472
4122
|
async buildInput(options) {
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
4123
|
+
const baseInput = messagesToInput(this.messages);
|
|
4124
|
+
if (!options.contextManagement || options.contextManagement === "none") {
|
|
4125
|
+
return baseInput;
|
|
4126
|
+
}
|
|
4127
|
+
const modelId = options.model ?? this.defaultModel;
|
|
4128
|
+
if (!modelId) {
|
|
4129
|
+
throw new ConfigError(
|
|
4130
|
+
"model is required for context management; set options.model or a session defaultModel"
|
|
4131
|
+
);
|
|
4132
|
+
}
|
|
4133
|
+
return prepareInputWithContext(
|
|
4134
|
+
baseInput,
|
|
4135
|
+
{
|
|
4136
|
+
model: modelId,
|
|
4137
|
+
strategy: options.contextManagement,
|
|
4138
|
+
maxHistoryTokens: options.maxHistoryTokens,
|
|
4139
|
+
reserveOutputTokens: options.reserveOutputTokens,
|
|
4140
|
+
onTruncate: options.onContextTruncate
|
|
4141
|
+
},
|
|
3477
4142
|
this.resolveModelContext
|
|
3478
4143
|
);
|
|
3479
4144
|
}
|
|
@@ -3482,13 +4147,7 @@ var RemoteSession = class _RemoteSession {
|
|
|
3482
4147
|
this.currentNodeId = void 0;
|
|
3483
4148
|
this.currentWaiting = void 0;
|
|
3484
4149
|
this.currentEvents = [];
|
|
3485
|
-
this.currentUsage =
|
|
3486
|
-
inputTokens: 0,
|
|
3487
|
-
outputTokens: 0,
|
|
3488
|
-
totalTokens: 0,
|
|
3489
|
-
llmCalls: 0,
|
|
3490
|
-
toolCalls: 0
|
|
3491
|
-
};
|
|
4150
|
+
this.currentUsage = emptyUsage();
|
|
3492
4151
|
}
|
|
3493
4152
|
async processRunEvents(signal) {
|
|
3494
4153
|
if (!this.currentRunId) {
|
|
@@ -3727,26 +4386,6 @@ var RemoteSession = class _RemoteSession {
|
|
|
3727
4386
|
return text.trim() ? text : void 0;
|
|
3728
4387
|
}
|
|
3729
4388
|
};
|
|
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
4389
|
|
|
3751
4390
|
// src/sessions/client.ts
|
|
3752
4391
|
var SessionsClient = class {
|
|
@@ -3863,111 +4502,1487 @@ var SessionsClient = class {
|
|
|
3863
4502
|
customerId: options.customerId
|
|
3864
4503
|
});
|
|
3865
4504
|
}
|
|
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);
|
|
4505
|
+
/**
|
|
4506
|
+
* Delete a remote session.
|
|
4507
|
+
*
|
|
4508
|
+
* Requires a secret key.
|
|
4509
|
+
*
|
|
4510
|
+
* @param sessionId - ID of the session to delete
|
|
4511
|
+
*
|
|
4512
|
+
* @example
|
|
4513
|
+
* ```typescript
|
|
4514
|
+
* await client.sessions.delete("session-id");
|
|
4515
|
+
* ```
|
|
4516
|
+
*/
|
|
4517
|
+
async delete(sessionId) {
|
|
4518
|
+
return RemoteSession.delete(this.modelRelay, sessionId);
|
|
4519
|
+
}
|
|
4520
|
+
};
|
|
4521
|
+
|
|
4522
|
+
// src/tiers.ts
|
|
4523
|
+
function defaultTierModelId(tier) {
|
|
4524
|
+
const def = tier.models.find((m) => m.is_default);
|
|
4525
|
+
if (def) return def.model_id;
|
|
4526
|
+
if (tier.models.length === 1) return tier.models[0].model_id;
|
|
4527
|
+
return void 0;
|
|
4528
|
+
}
|
|
4529
|
+
var TiersClient = class {
|
|
4530
|
+
constructor(http, cfg) {
|
|
4531
|
+
this.http = http;
|
|
4532
|
+
this.apiKey = cfg.apiKey ? parseApiKey(cfg.apiKey) : void 0;
|
|
4533
|
+
this.hasSecretKey = this.apiKey ? isSecretKey(this.apiKey) : false;
|
|
4534
|
+
this.hasAccessToken = !!cfg.accessToken?.trim();
|
|
4535
|
+
}
|
|
4536
|
+
ensureAuth() {
|
|
4537
|
+
if (!this.apiKey && !this.hasAccessToken) {
|
|
4538
|
+
throw new ConfigError(
|
|
4539
|
+
"API key (mr_sk_*) or bearer token required for tier operations"
|
|
4540
|
+
);
|
|
4541
|
+
}
|
|
4542
|
+
}
|
|
4543
|
+
ensureSecretKey() {
|
|
4544
|
+
if (!this.apiKey || !this.hasSecretKey) {
|
|
4545
|
+
throw new ConfigError(
|
|
4546
|
+
"Secret key (mr_sk_*) required for checkout operations"
|
|
4547
|
+
);
|
|
4548
|
+
}
|
|
4549
|
+
}
|
|
4550
|
+
/**
|
|
4551
|
+
* List all tiers in the project.
|
|
4552
|
+
*/
|
|
4553
|
+
async list() {
|
|
4554
|
+
this.ensureAuth();
|
|
4555
|
+
const response = await this.http.json("/tiers", {
|
|
4556
|
+
method: "GET",
|
|
4557
|
+
apiKey: this.apiKey
|
|
4558
|
+
});
|
|
4559
|
+
return response.tiers;
|
|
4560
|
+
}
|
|
4561
|
+
/**
|
|
4562
|
+
* Get a tier by ID.
|
|
4563
|
+
*/
|
|
4564
|
+
async get(tierId) {
|
|
4565
|
+
this.ensureAuth();
|
|
4566
|
+
if (!tierId?.trim()) {
|
|
4567
|
+
throw new ConfigError("tierId is required");
|
|
4568
|
+
}
|
|
4569
|
+
const response = await this.http.json(`/tiers/${tierId}`, {
|
|
4570
|
+
method: "GET",
|
|
4571
|
+
apiKey: this.apiKey
|
|
4572
|
+
});
|
|
4573
|
+
return response.tier;
|
|
4574
|
+
}
|
|
4575
|
+
/**
|
|
4576
|
+
* Create a Stripe checkout session for a tier (Stripe-first flow).
|
|
4577
|
+
*
|
|
4578
|
+
* This enables users to subscribe before authenticating. Stripe collects
|
|
4579
|
+
* the customer's email during checkout. After checkout completes, a
|
|
4580
|
+
* customer record is created with the email from Stripe. Your backend
|
|
4581
|
+
* can map it to your app user and mint customer tokens as needed.
|
|
4582
|
+
*
|
|
4583
|
+
* Requires a secret key (mr_sk_*).
|
|
4584
|
+
*
|
|
4585
|
+
* @param tierId - The tier ID to create a checkout session for
|
|
4586
|
+
* @param request - Checkout session request with redirect URLs
|
|
4587
|
+
* @returns Checkout session with Stripe URL
|
|
4588
|
+
*/
|
|
4589
|
+
async checkout(tierId, request) {
|
|
4590
|
+
this.ensureSecretKey();
|
|
4591
|
+
if (!tierId?.trim()) {
|
|
4592
|
+
throw new ConfigError("tierId is required");
|
|
4593
|
+
}
|
|
4594
|
+
if (!request.success_url?.trim()) {
|
|
4595
|
+
throw new ConfigError("success_url is required");
|
|
4596
|
+
}
|
|
4597
|
+
if (!request.cancel_url?.trim()) {
|
|
4598
|
+
throw new ConfigError("cancel_url is required");
|
|
4599
|
+
}
|
|
4600
|
+
return await this.http.json(
|
|
4601
|
+
`/tiers/${tierId}/checkout`,
|
|
4602
|
+
{
|
|
4603
|
+
method: "POST",
|
|
4604
|
+
apiKey: this.apiKey,
|
|
4605
|
+
body: request
|
|
4606
|
+
}
|
|
4607
|
+
);
|
|
4608
|
+
}
|
|
4609
|
+
};
|
|
4610
|
+
|
|
4611
|
+
// src/plugins.ts
|
|
4612
|
+
import { z as z2 } from "zod";
|
|
4613
|
+
|
|
4614
|
+
// src/tools_user_ask.ts
|
|
4615
|
+
var USER_ASK_TOOL_NAME = "user_ask";
|
|
4616
|
+
var userAskSchema = {
|
|
4617
|
+
type: "object",
|
|
4618
|
+
properties: {
|
|
4619
|
+
question: {
|
|
4620
|
+
type: "string",
|
|
4621
|
+
minLength: 1,
|
|
4622
|
+
description: "The question to ask the user."
|
|
4623
|
+
},
|
|
4624
|
+
options: {
|
|
4625
|
+
type: "array",
|
|
4626
|
+
items: {
|
|
4627
|
+
type: "object",
|
|
4628
|
+
properties: {
|
|
4629
|
+
label: { type: "string", minLength: 1 },
|
|
4630
|
+
description: { type: "string" }
|
|
4631
|
+
},
|
|
4632
|
+
required: ["label"]
|
|
4633
|
+
},
|
|
4634
|
+
description: "Optional multiple choice options."
|
|
4635
|
+
},
|
|
4636
|
+
allow_freeform: {
|
|
4637
|
+
type: "boolean",
|
|
4638
|
+
default: true,
|
|
4639
|
+
description: "Allow user to type a custom response."
|
|
4640
|
+
}
|
|
4641
|
+
},
|
|
4642
|
+
required: ["question"]
|
|
4643
|
+
};
|
|
4644
|
+
function createUserAskTool() {
|
|
4645
|
+
return createFunctionTool(
|
|
4646
|
+
USER_ASK_TOOL_NAME,
|
|
4647
|
+
"Ask the user a clarifying question.",
|
|
4648
|
+
userAskSchema
|
|
4649
|
+
);
|
|
4650
|
+
}
|
|
4651
|
+
function isUserAskToolCall(call) {
|
|
4652
|
+
return call.type === "function" && call.function?.name === USER_ASK_TOOL_NAME;
|
|
4653
|
+
}
|
|
4654
|
+
function parseUserAskArgs(call) {
|
|
4655
|
+
const raw = getToolArgsRaw(call);
|
|
4656
|
+
if (!raw) {
|
|
4657
|
+
throw new ToolArgumentError({
|
|
4658
|
+
message: "user_ask arguments required",
|
|
4659
|
+
toolCallId: call.id,
|
|
4660
|
+
toolName: USER_ASK_TOOL_NAME,
|
|
4661
|
+
rawArguments: raw
|
|
4662
|
+
});
|
|
4663
|
+
}
|
|
4664
|
+
let parsed;
|
|
4665
|
+
try {
|
|
4666
|
+
parsed = JSON.parse(raw);
|
|
4667
|
+
} catch (err) {
|
|
4668
|
+
throw new ToolArgumentError({
|
|
4669
|
+
message: "user_ask arguments must be valid JSON",
|
|
4670
|
+
toolCallId: call.id,
|
|
4671
|
+
toolName: USER_ASK_TOOL_NAME,
|
|
4672
|
+
rawArguments: raw,
|
|
4673
|
+
cause: err
|
|
4674
|
+
});
|
|
4675
|
+
}
|
|
4676
|
+
const question = parsed.question?.trim?.() ?? "";
|
|
4677
|
+
if (!question) {
|
|
4678
|
+
throw new ToolArgumentError({
|
|
4679
|
+
message: "user_ask question required",
|
|
4680
|
+
toolCallId: call.id,
|
|
4681
|
+
toolName: USER_ASK_TOOL_NAME,
|
|
4682
|
+
rawArguments: raw
|
|
4683
|
+
});
|
|
4684
|
+
}
|
|
4685
|
+
if (parsed.options?.length) {
|
|
4686
|
+
for (const opt of parsed.options) {
|
|
4687
|
+
if (!opt?.label?.trim?.()) {
|
|
4688
|
+
throw new ToolArgumentError({
|
|
4689
|
+
message: "user_ask options require label",
|
|
4690
|
+
toolCallId: call.id,
|
|
4691
|
+
toolName: USER_ASK_TOOL_NAME,
|
|
4692
|
+
rawArguments: raw
|
|
4693
|
+
});
|
|
4694
|
+
}
|
|
4695
|
+
}
|
|
4696
|
+
}
|
|
4697
|
+
return {
|
|
4698
|
+
question,
|
|
4699
|
+
options: parsed.options,
|
|
4700
|
+
allow_freeform: parsed.allow_freeform
|
|
4701
|
+
};
|
|
4702
|
+
}
|
|
4703
|
+
function serializeUserAskResult(result) {
|
|
4704
|
+
const answer = result.answer?.trim?.() ?? "";
|
|
4705
|
+
if (!answer) {
|
|
4706
|
+
throw new ToolArgumentError({
|
|
4707
|
+
message: "user_ask answer required",
|
|
4708
|
+
toolCallId: "",
|
|
4709
|
+
toolName: USER_ASK_TOOL_NAME,
|
|
4710
|
+
rawArguments: ""
|
|
4711
|
+
});
|
|
4712
|
+
}
|
|
4713
|
+
return JSON.stringify({ answer, is_freeform: result.is_freeform });
|
|
4714
|
+
}
|
|
4715
|
+
function userAskResultFreeform(answer) {
|
|
4716
|
+
return serializeUserAskResult({ answer, is_freeform: true });
|
|
4717
|
+
}
|
|
4718
|
+
function userAskResultChoice(answer) {
|
|
4719
|
+
return serializeUserAskResult({ answer, is_freeform: false });
|
|
4720
|
+
}
|
|
4721
|
+
|
|
4722
|
+
// src/tools_runner.ts
|
|
4723
|
+
var ToolRunner = class {
|
|
4724
|
+
constructor(options) {
|
|
4725
|
+
this.registry = options.registry;
|
|
4726
|
+
this.runsClient = options.runsClient;
|
|
4727
|
+
this.customerId = options.customerId;
|
|
4728
|
+
this.onBeforeExecute = options.onBeforeExecute;
|
|
4729
|
+
this.onAfterExecute = options.onAfterExecute;
|
|
4730
|
+
this.onSubmitted = options.onSubmitted;
|
|
4731
|
+
this.onUserAsk = options.onUserAsk;
|
|
4732
|
+
this.onError = options.onError;
|
|
4733
|
+
}
|
|
4734
|
+
/**
|
|
4735
|
+
* Handles a node_waiting event by executing tools and submitting results.
|
|
4736
|
+
*
|
|
4737
|
+
* @param runId - The run ID
|
|
4738
|
+
* @param nodeId - The node ID that is waiting
|
|
4739
|
+
* @param waiting - The waiting state with pending tool calls
|
|
4740
|
+
* @returns The submission response with accepted count and new status
|
|
4741
|
+
*
|
|
4742
|
+
* @example
|
|
4743
|
+
* ```typescript
|
|
4744
|
+
* for await (const event of client.runs.events(runId)) {
|
|
4745
|
+
* if (event.type === "node_waiting") {
|
|
4746
|
+
* const result = await runner.handleNodeWaiting(
|
|
4747
|
+
* runId,
|
|
4748
|
+
* event.node_id,
|
|
4749
|
+
* event.waiting
|
|
4750
|
+
* );
|
|
4751
|
+
* console.log(`Submitted ${result.accepted} results, status: ${result.status}`);
|
|
4752
|
+
* }
|
|
4753
|
+
* }
|
|
4754
|
+
* ```
|
|
4755
|
+
*/
|
|
4756
|
+
async handleNodeWaiting(runId, nodeId, waiting) {
|
|
4757
|
+
const results = [];
|
|
4758
|
+
for (const pending of waiting.pending_tool_calls) {
|
|
4759
|
+
try {
|
|
4760
|
+
await this.onBeforeExecute?.(pending);
|
|
4761
|
+
const toolCall = createToolCall(
|
|
4762
|
+
pending.tool_call.id,
|
|
4763
|
+
pending.tool_call.name,
|
|
4764
|
+
pending.tool_call.arguments
|
|
4765
|
+
);
|
|
4766
|
+
let result;
|
|
4767
|
+
if (pending.tool_call.name === USER_ASK_TOOL_NAME) {
|
|
4768
|
+
if (!this.onUserAsk) {
|
|
4769
|
+
throw new Error("user_ask requires onUserAsk handler");
|
|
4770
|
+
}
|
|
4771
|
+
const args = parseUserAskArgs(toolCall);
|
|
4772
|
+
const response2 = await this.onUserAsk(pending, args);
|
|
4773
|
+
const output = typeof response2 === "string" ? serializeUserAskResult({ answer: response2, is_freeform: true }) : serializeUserAskResult(response2);
|
|
4774
|
+
result = {
|
|
4775
|
+
toolCallId: pending.tool_call.id,
|
|
4776
|
+
toolName: pending.tool_call.name,
|
|
4777
|
+
result: output,
|
|
4778
|
+
isRetryable: false
|
|
4779
|
+
};
|
|
4780
|
+
} else {
|
|
4781
|
+
result = await this.registry.execute(toolCall);
|
|
4782
|
+
}
|
|
4783
|
+
results.push(result);
|
|
4784
|
+
await this.onAfterExecute?.(result);
|
|
4785
|
+
} catch (err) {
|
|
4786
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
4787
|
+
await this.onError?.(error, pending);
|
|
4788
|
+
results.push({
|
|
4789
|
+
toolCallId: pending.tool_call.id,
|
|
4790
|
+
toolName: pending.tool_call.name,
|
|
4791
|
+
result: null,
|
|
4792
|
+
error: error.message
|
|
4793
|
+
});
|
|
4794
|
+
}
|
|
4795
|
+
}
|
|
4796
|
+
const response = await this.runsClient.submitToolResults(
|
|
4797
|
+
runId,
|
|
4798
|
+
{
|
|
4799
|
+
node_id: nodeId,
|
|
4800
|
+
step: waiting.step,
|
|
4801
|
+
request_id: waiting.request_id,
|
|
4802
|
+
results: results.map((r) => ({
|
|
4803
|
+
tool_call: {
|
|
4804
|
+
id: r.toolCallId,
|
|
4805
|
+
name: r.toolName
|
|
4806
|
+
},
|
|
4807
|
+
output: r.error ? `Error: ${r.error}` : typeof r.result === "string" ? r.result : JSON.stringify(r.result)
|
|
4808
|
+
}))
|
|
4809
|
+
},
|
|
4810
|
+
{ customerId: this.customerId }
|
|
4811
|
+
);
|
|
4812
|
+
await this.onSubmitted?.(runId, response.accepted, response.status);
|
|
4813
|
+
return {
|
|
4814
|
+
accepted: response.accepted,
|
|
4815
|
+
status: response.status,
|
|
4816
|
+
results
|
|
4817
|
+
};
|
|
4818
|
+
}
|
|
4819
|
+
/**
|
|
4820
|
+
* Processes a stream of run events, automatically handling node_waiting events.
|
|
4821
|
+
*
|
|
4822
|
+
* This is the main entry point for running a workflow with client-side tools.
|
|
4823
|
+
* It yields all events through (including node_waiting after handling).
|
|
4824
|
+
*
|
|
4825
|
+
* @param runId - The run ID to process
|
|
4826
|
+
* @param events - AsyncIterable of run events (from RunsClient.events())
|
|
4827
|
+
* @yields All run events, with node_waiting events handled automatically
|
|
4828
|
+
*
|
|
4829
|
+
* @example
|
|
4830
|
+
* ```typescript
|
|
4831
|
+
* const run = await client.runs.create(workflowSpec);
|
|
4832
|
+
* const eventStream = client.runs.events(run.run_id);
|
|
4833
|
+
*
|
|
4834
|
+
* for await (const event of runner.processEvents(run.run_id, eventStream)) {
|
|
4835
|
+
* switch (event.type) {
|
|
4836
|
+
* case "node_started":
|
|
4837
|
+
* console.log(`Node ${event.node_id} started`);
|
|
4838
|
+
* break;
|
|
4839
|
+
* case "node_succeeded":
|
|
4840
|
+
* console.log(`Node ${event.node_id} succeeded`);
|
|
4841
|
+
* break;
|
|
4842
|
+
* case "run_succeeded":
|
|
4843
|
+
* console.log("Run completed!");
|
|
4844
|
+
* break;
|
|
4845
|
+
* }
|
|
4846
|
+
* }
|
|
4847
|
+
* ```
|
|
4848
|
+
*/
|
|
4849
|
+
async *processEvents(runId, events) {
|
|
4850
|
+
for await (const event of events) {
|
|
4851
|
+
if (event.type === "node_waiting") {
|
|
4852
|
+
const waitingEvent = event;
|
|
4853
|
+
try {
|
|
4854
|
+
await this.handleNodeWaiting(
|
|
4855
|
+
runId,
|
|
4856
|
+
waitingEvent.node_id,
|
|
4857
|
+
waitingEvent.waiting
|
|
4858
|
+
);
|
|
4859
|
+
} catch (err) {
|
|
4860
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
4861
|
+
await this.onError?.(error);
|
|
4862
|
+
throw error;
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
yield event;
|
|
4866
|
+
}
|
|
4867
|
+
}
|
|
4868
|
+
/**
|
|
4869
|
+
* Checks if a run event is a node_waiting event.
|
|
4870
|
+
* Utility for filtering events when not using processEvents().
|
|
4871
|
+
*/
|
|
4872
|
+
static isNodeWaiting(event) {
|
|
4873
|
+
return event.type === "node_waiting";
|
|
4874
|
+
}
|
|
4875
|
+
/**
|
|
4876
|
+
* Checks if a run status is terminal (succeeded, failed, or canceled).
|
|
4877
|
+
* Utility for determining when to stop polling.
|
|
4878
|
+
*/
|
|
4879
|
+
static isTerminalStatus(status) {
|
|
4880
|
+
return status === "succeeded" || status === "failed" || status === "canceled";
|
|
4881
|
+
}
|
|
4882
|
+
};
|
|
4883
|
+
function createToolRunner(options) {
|
|
4884
|
+
return new ToolRunner(options);
|
|
4885
|
+
}
|
|
4886
|
+
|
|
4887
|
+
// src/plugins.ts
|
|
4888
|
+
var PluginToolNames = {
|
|
4889
|
+
FS_READ_FILE: "fs_read_file",
|
|
4890
|
+
FS_LIST_FILES: "fs_list_files",
|
|
4891
|
+
FS_SEARCH: "fs_search",
|
|
4892
|
+
FS_EDIT: "fs_edit",
|
|
4893
|
+
BASH: "bash",
|
|
4894
|
+
WRITE_FILE: "write_file",
|
|
4895
|
+
USER_ASK: "user_ask"
|
|
4896
|
+
};
|
|
4897
|
+
var OrchestrationModes = {
|
|
4898
|
+
DAG: "dag",
|
|
4899
|
+
Dynamic: "dynamic"
|
|
4900
|
+
};
|
|
4901
|
+
var PluginOrchestrationErrorCodes = {
|
|
4902
|
+
InvalidPlan: "INVALID_PLAN",
|
|
4903
|
+
UnknownAgent: "UNKNOWN_AGENT",
|
|
4904
|
+
MissingDescription: "MISSING_DESCRIPTION",
|
|
4905
|
+
UnknownTool: "UNKNOWN_TOOL",
|
|
4906
|
+
InvalidDependency: "INVALID_DEPENDENCY",
|
|
4907
|
+
InvalidToolConfig: "INVALID_TOOL_CONFIG"
|
|
4908
|
+
};
|
|
4909
|
+
var PluginOrchestrationError = class extends Error {
|
|
4910
|
+
constructor(code, message) {
|
|
4911
|
+
super(`plugin orchestration: ${message}`);
|
|
4912
|
+
this.code = code;
|
|
4913
|
+
}
|
|
4914
|
+
};
|
|
4915
|
+
var DEFAULT_PLUGIN_REF = "HEAD";
|
|
4916
|
+
var DEFAULT_CACHE_TTL_MS = 5 * 6e4;
|
|
4917
|
+
var DEFAULT_GITHUB_API_BASE = "https://api.github.com";
|
|
4918
|
+
var DEFAULT_GITHUB_RAW_BASE = "https://raw.githubusercontent.com";
|
|
4919
|
+
var defaultDynamicToolNames = [
|
|
4920
|
+
PluginToolNames.FS_READ_FILE,
|
|
4921
|
+
PluginToolNames.FS_LIST_FILES,
|
|
4922
|
+
PluginToolNames.FS_SEARCH
|
|
4923
|
+
];
|
|
4924
|
+
var allowedToolSet = new Set(Object.values(PluginToolNames));
|
|
4925
|
+
var workflowIntentSchema = z2.object({
|
|
4926
|
+
kind: z2.literal(WorkflowKinds.WorkflowIntent),
|
|
4927
|
+
name: z2.string().optional(),
|
|
4928
|
+
model: z2.string().optional(),
|
|
4929
|
+
max_parallelism: z2.number().int().positive().optional(),
|
|
4930
|
+
inputs: z2.array(
|
|
4931
|
+
z2.object({
|
|
4932
|
+
name: z2.string().min(1),
|
|
4933
|
+
type: z2.string().optional(),
|
|
4934
|
+
required: z2.boolean().optional(),
|
|
4935
|
+
description: z2.string().optional(),
|
|
4936
|
+
default: z2.unknown().optional()
|
|
4937
|
+
})
|
|
4938
|
+
).optional(),
|
|
4939
|
+
nodes: z2.array(
|
|
4940
|
+
z2.object({
|
|
4941
|
+
id: z2.string().min(1),
|
|
4942
|
+
type: z2.enum([
|
|
4943
|
+
WorkflowNodeTypesIntent.LLM,
|
|
4944
|
+
WorkflowNodeTypesIntent.JoinAll,
|
|
4945
|
+
WorkflowNodeTypesIntent.JoinAny,
|
|
4946
|
+
WorkflowNodeTypesIntent.JoinCollect,
|
|
4947
|
+
WorkflowNodeTypesIntent.TransformJSON,
|
|
4948
|
+
WorkflowNodeTypesIntent.MapFanout
|
|
4949
|
+
]),
|
|
4950
|
+
depends_on: z2.array(z2.string().min(1)).optional(),
|
|
4951
|
+
model: z2.string().optional(),
|
|
4952
|
+
system: z2.string().optional(),
|
|
4953
|
+
user: z2.string().optional(),
|
|
4954
|
+
input: z2.array(z2.unknown()).optional(),
|
|
4955
|
+
stream: z2.boolean().optional(),
|
|
4956
|
+
tools: z2.array(
|
|
4957
|
+
z2.union([
|
|
4958
|
+
z2.string(),
|
|
4959
|
+
z2.object({}).passthrough()
|
|
4960
|
+
])
|
|
4961
|
+
).optional(),
|
|
4962
|
+
tool_execution: z2.object({ mode: z2.enum(["server", "client", "agentic"]) }).optional(),
|
|
4963
|
+
limit: z2.number().int().positive().optional(),
|
|
4964
|
+
timeout_ms: z2.number().int().positive().optional(),
|
|
4965
|
+
predicate: z2.object({}).passthrough().optional(),
|
|
4966
|
+
items_from: z2.string().optional(),
|
|
4967
|
+
items_from_input: z2.string().optional(),
|
|
4968
|
+
items_pointer: z2.string().optional(),
|
|
4969
|
+
items_path: z2.string().optional(),
|
|
4970
|
+
subnode: z2.object({}).passthrough().optional(),
|
|
4971
|
+
max_parallelism: z2.number().int().positive().optional(),
|
|
4972
|
+
object: z2.record(z2.unknown()).optional(),
|
|
4973
|
+
merge: z2.array(z2.unknown()).optional()
|
|
4974
|
+
}).passthrough()
|
|
4975
|
+
).min(1),
|
|
4976
|
+
outputs: z2.array(
|
|
4977
|
+
z2.object({
|
|
4978
|
+
name: z2.string().min(1),
|
|
4979
|
+
from: z2.string().min(1),
|
|
4980
|
+
pointer: z2.string().optional()
|
|
4981
|
+
})
|
|
4982
|
+
).min(1)
|
|
4983
|
+
}).passthrough();
|
|
4984
|
+
var orchestrationPlanSchema = z2.object({
|
|
4985
|
+
kind: z2.literal("orchestration.plan.v1"),
|
|
4986
|
+
max_parallelism: z2.number().int().positive().optional(),
|
|
4987
|
+
steps: z2.array(
|
|
4988
|
+
z2.object({
|
|
4989
|
+
id: z2.string().min(1).optional(),
|
|
4990
|
+
depends_on: z2.array(z2.string().min(1)).optional(),
|
|
4991
|
+
agents: z2.array(
|
|
4992
|
+
z2.object({
|
|
4993
|
+
id: z2.string().min(1),
|
|
4994
|
+
reason: z2.string().min(1)
|
|
4995
|
+
})
|
|
4996
|
+
).min(1)
|
|
4997
|
+
}).strict()
|
|
4998
|
+
).min(1)
|
|
4999
|
+
}).strict();
|
|
5000
|
+
var PluginLoader = class {
|
|
5001
|
+
constructor(options = {}) {
|
|
5002
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
5003
|
+
this.fetchFn = options.fetch || globalThis.fetch;
|
|
5004
|
+
if (!this.fetchFn) {
|
|
5005
|
+
throw new ConfigError("fetch is required to load plugins");
|
|
5006
|
+
}
|
|
5007
|
+
this.apiBaseUrl = options.apiBaseUrl || DEFAULT_GITHUB_API_BASE;
|
|
5008
|
+
this.rawBaseUrl = options.rawBaseUrl || DEFAULT_GITHUB_RAW_BASE;
|
|
5009
|
+
this.cacheTtlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
5010
|
+
this.now = options.now || (() => /* @__PURE__ */ new Date());
|
|
5011
|
+
}
|
|
5012
|
+
async load(sourceUrl, options = {}) {
|
|
5013
|
+
const ref = parseGitHubPluginRef(sourceUrl);
|
|
5014
|
+
const key = ref.canonical;
|
|
5015
|
+
const cached = this.cache.get(key);
|
|
5016
|
+
if (cached && cached.expiresAt > this.now().getTime()) {
|
|
5017
|
+
return clonePlugin(cached.plugin);
|
|
5018
|
+
}
|
|
5019
|
+
const manifestCandidates = ["PLUGIN.md", "SKILL.md"];
|
|
5020
|
+
let manifestPath = "";
|
|
5021
|
+
let manifestMd = "";
|
|
5022
|
+
for (const candidate of manifestCandidates) {
|
|
5023
|
+
const path = joinRepoPath(ref.repoPath, candidate);
|
|
5024
|
+
const url = this.rawUrl(ref, path);
|
|
5025
|
+
const res = await this.fetchText(url, options.signal);
|
|
5026
|
+
if (res.status === 404) {
|
|
5027
|
+
continue;
|
|
5028
|
+
}
|
|
5029
|
+
if (!res.ok) {
|
|
5030
|
+
throw new ConfigError(`fetch ${path}: ${res.statusText}`);
|
|
5031
|
+
}
|
|
5032
|
+
manifestPath = path;
|
|
5033
|
+
manifestMd = res.body;
|
|
5034
|
+
break;
|
|
5035
|
+
}
|
|
5036
|
+
if (!manifestPath) {
|
|
5037
|
+
throw new ConfigError("plugin manifest not found");
|
|
5038
|
+
}
|
|
5039
|
+
const commandsDir = joinRepoPath(ref.repoPath, "commands");
|
|
5040
|
+
const agentsDir = joinRepoPath(ref.repoPath, "agents");
|
|
5041
|
+
const commandFiles = await this.listMarkdownFiles(ref, commandsDir, options.signal);
|
|
5042
|
+
const agentFiles = await this.listMarkdownFiles(ref, agentsDir, options.signal);
|
|
5043
|
+
const plugin = {
|
|
5044
|
+
id: asPluginId(`${ref.owner}/${ref.repo}${ref.repoPath ? `/${ref.repoPath}` : ""}`),
|
|
5045
|
+
url: asPluginUrl(ref.canonical),
|
|
5046
|
+
manifest: parsePluginManifest(manifestMd),
|
|
5047
|
+
commands: {},
|
|
5048
|
+
agents: {},
|
|
5049
|
+
rawFiles: { [manifestPath]: manifestMd },
|
|
5050
|
+
ref: {
|
|
5051
|
+
owner: ref.owner,
|
|
5052
|
+
repo: ref.repo,
|
|
5053
|
+
ref: ref.ref,
|
|
5054
|
+
path: ref.repoPath || void 0
|
|
5055
|
+
},
|
|
5056
|
+
loadedAt: this.now()
|
|
5057
|
+
};
|
|
5058
|
+
for (const filePath of commandFiles) {
|
|
5059
|
+
const res = await this.fetchText(this.rawUrl(ref, filePath), options.signal);
|
|
5060
|
+
if (!res.ok) {
|
|
5061
|
+
throw new ConfigError(`fetch ${filePath}: ${res.statusText}`);
|
|
5062
|
+
}
|
|
5063
|
+
const { tools, body } = parseMarkdownFrontMatter(res.body);
|
|
5064
|
+
const name = asPluginCommandName(basename(filePath));
|
|
5065
|
+
plugin.commands[String(name)] = {
|
|
5066
|
+
name,
|
|
5067
|
+
prompt: body,
|
|
5068
|
+
agentRefs: extractAgentRefs(body),
|
|
5069
|
+
tools
|
|
5070
|
+
};
|
|
5071
|
+
plugin.rawFiles[filePath] = res.body;
|
|
5072
|
+
}
|
|
5073
|
+
for (const filePath of agentFiles) {
|
|
5074
|
+
const res = await this.fetchText(this.rawUrl(ref, filePath), options.signal);
|
|
5075
|
+
if (!res.ok) {
|
|
5076
|
+
throw new ConfigError(`fetch ${filePath}: ${res.statusText}`);
|
|
5077
|
+
}
|
|
5078
|
+
const { description, tools, body } = parseMarkdownFrontMatter(res.body);
|
|
5079
|
+
const name = asPluginAgentName(basename(filePath));
|
|
5080
|
+
plugin.agents[String(name)] = {
|
|
5081
|
+
name,
|
|
5082
|
+
systemPrompt: body,
|
|
5083
|
+
description,
|
|
5084
|
+
tools
|
|
5085
|
+
};
|
|
5086
|
+
plugin.rawFiles[filePath] = res.body;
|
|
5087
|
+
}
|
|
5088
|
+
plugin.manifest.commands = sortedKeys(Object.values(plugin.commands).map((c) => c.name));
|
|
5089
|
+
plugin.manifest.agents = sortedKeys(Object.values(plugin.agents).map((a) => a.name));
|
|
5090
|
+
this.cache.set(key, {
|
|
5091
|
+
expiresAt: this.now().getTime() + this.cacheTtlMs,
|
|
5092
|
+
plugin: clonePlugin(plugin)
|
|
5093
|
+
});
|
|
5094
|
+
return clonePlugin(plugin);
|
|
5095
|
+
}
|
|
5096
|
+
async listMarkdownFiles(ref, repoDir, signal) {
|
|
5097
|
+
const path = `/repos/${ref.owner}/${ref.repo}/contents/${repoDir}`;
|
|
5098
|
+
const url = `${this.apiBaseUrl}${path}?ref=${encodeURIComponent(ref.ref)}`;
|
|
5099
|
+
const res = await this.fetchJson(url, signal);
|
|
5100
|
+
if (res.status === 404) {
|
|
5101
|
+
return [];
|
|
5102
|
+
}
|
|
5103
|
+
if (!res.ok) {
|
|
5104
|
+
throw new ConfigError(`fetch ${repoDir}: ${res.statusText}`);
|
|
5105
|
+
}
|
|
5106
|
+
return res.body.filter((entry) => entry.type === "file" && entry.name.endsWith(".md")).map((entry) => entry.path);
|
|
5107
|
+
}
|
|
5108
|
+
rawUrl(ref, repoPath) {
|
|
5109
|
+
const cleaned = repoPath.replace(/^\/+/, "");
|
|
5110
|
+
return `${this.rawBaseUrl}/${ref.owner}/${ref.repo}/${ref.ref}/${cleaned}`;
|
|
5111
|
+
}
|
|
5112
|
+
async fetchText(url, signal) {
|
|
5113
|
+
const res = await this.fetchFn(url, { signal });
|
|
5114
|
+
const body = await res.text();
|
|
5115
|
+
return { ok: res.ok, status: res.status, statusText: res.statusText, body };
|
|
5116
|
+
}
|
|
5117
|
+
async fetchJson(url, signal) {
|
|
5118
|
+
const res = await this.fetchFn(url, { signal });
|
|
5119
|
+
if (!res.ok) {
|
|
5120
|
+
return {
|
|
5121
|
+
ok: res.ok,
|
|
5122
|
+
status: res.status,
|
|
5123
|
+
statusText: res.statusText,
|
|
5124
|
+
body: []
|
|
5125
|
+
};
|
|
5126
|
+
}
|
|
5127
|
+
const body = await res.json();
|
|
5128
|
+
return { ok: res.ok, status: res.status, statusText: res.statusText, body };
|
|
5129
|
+
}
|
|
5130
|
+
};
|
|
5131
|
+
var PluginConverter = class {
|
|
5132
|
+
constructor(responses, http, auth, options = {}) {
|
|
5133
|
+
this.responses = responses;
|
|
5134
|
+
this.http = http;
|
|
5135
|
+
this.auth = auth;
|
|
5136
|
+
this.converterModel = asModelId(options.converterModel || "claude-3-5-haiku-latest");
|
|
5137
|
+
}
|
|
5138
|
+
async toWorkflow(plugin, commandName, task) {
|
|
5139
|
+
const command = resolveCommand(plugin, commandName);
|
|
5140
|
+
const prompt = buildPluginConversionPrompt(plugin, command, task);
|
|
5141
|
+
const schemaName = "workflow";
|
|
5142
|
+
const result = await this.responses.object({
|
|
5143
|
+
model: this.converterModel,
|
|
5144
|
+
schema: workflowIntentSchema,
|
|
5145
|
+
schemaName,
|
|
5146
|
+
system: pluginToWorkflowSystemPrompt,
|
|
5147
|
+
prompt
|
|
5148
|
+
});
|
|
5149
|
+
const spec = normalizeWorkflowIntent(result);
|
|
5150
|
+
validateWorkflowTools(spec);
|
|
5151
|
+
return spec;
|
|
5152
|
+
}
|
|
5153
|
+
async toWorkflowDynamic(plugin, commandName, task) {
|
|
5154
|
+
const command = resolveCommand(plugin, commandName);
|
|
5155
|
+
const { candidates, lookup } = buildOrchestrationCandidates(plugin, command);
|
|
5156
|
+
const prompt = buildPluginOrchestrationPrompt(plugin, command, task, candidates);
|
|
5157
|
+
const plan = await this.responses.object({
|
|
5158
|
+
model: this.converterModel,
|
|
5159
|
+
schema: orchestrationPlanSchema,
|
|
5160
|
+
schemaName: "orchestration_plan",
|
|
5161
|
+
system: pluginOrchestrationSystemPrompt,
|
|
5162
|
+
prompt
|
|
5163
|
+
});
|
|
5164
|
+
validateOrchestrationPlan(plan, lookup);
|
|
5165
|
+
const spec = buildDynamicWorkflowFromPlan(plugin, command, task, plan, lookup, this.converterModel);
|
|
5166
|
+
if (specRequiresTools(spec)) {
|
|
5167
|
+
await ensureModelSupportsTools(this.http, this.auth, this.converterModel);
|
|
5168
|
+
}
|
|
5169
|
+
validateWorkflowTools(spec);
|
|
5170
|
+
return spec;
|
|
5171
|
+
}
|
|
5172
|
+
};
|
|
5173
|
+
var PluginRunner = class {
|
|
5174
|
+
constructor(runs) {
|
|
5175
|
+
this.runs = runs;
|
|
5176
|
+
}
|
|
5177
|
+
async run(spec, config) {
|
|
5178
|
+
const created = await this.runs.create(spec, config.runOptions);
|
|
5179
|
+
return this.wait(created.run_id, config);
|
|
5180
|
+
}
|
|
5181
|
+
async wait(runId, config) {
|
|
5182
|
+
const events = [];
|
|
5183
|
+
const toolRegistry = config.toolRegistry;
|
|
5184
|
+
const eventStream = await this.runs.events(runId, config.runOptions);
|
|
5185
|
+
const runner = toolRegistry ? new ToolRunner({ registry: toolRegistry, runsClient: this.runs }) : null;
|
|
5186
|
+
const stream = runner ? runner.processEvents(runId, eventStream) : eventStream;
|
|
5187
|
+
let terminal = null;
|
|
5188
|
+
for await (const event of stream) {
|
|
5189
|
+
events.push(event);
|
|
5190
|
+
if (event.type === "run_completed") {
|
|
5191
|
+
terminal = "succeeded";
|
|
5192
|
+
break;
|
|
5193
|
+
}
|
|
5194
|
+
if (event.type === "run_failed") {
|
|
5195
|
+
terminal = "failed";
|
|
5196
|
+
break;
|
|
5197
|
+
}
|
|
5198
|
+
if (event.type === "run_canceled") {
|
|
5199
|
+
terminal = "canceled";
|
|
5200
|
+
break;
|
|
5201
|
+
}
|
|
5202
|
+
}
|
|
5203
|
+
const snapshot = await this.runs.get(runId, config.runOptions);
|
|
5204
|
+
return {
|
|
5205
|
+
runId: snapshot.run_id,
|
|
5206
|
+
status: terminal || snapshot.status,
|
|
5207
|
+
outputs: snapshot.outputs,
|
|
5208
|
+
costSummary: snapshot.cost_summary,
|
|
5209
|
+
events
|
|
5210
|
+
};
|
|
5211
|
+
}
|
|
5212
|
+
};
|
|
5213
|
+
var PluginsClient = class {
|
|
5214
|
+
constructor(deps) {
|
|
5215
|
+
this.responses = deps.responses;
|
|
5216
|
+
this.http = deps.http;
|
|
5217
|
+
this.auth = deps.auth;
|
|
5218
|
+
this.loader = new PluginLoader(deps.options);
|
|
5219
|
+
this.converter = new PluginConverter(deps.responses, deps.http, deps.auth);
|
|
5220
|
+
this.runner = new PluginRunner(deps.runs);
|
|
5221
|
+
}
|
|
5222
|
+
load(url, options) {
|
|
5223
|
+
return this.loader.load(url, options);
|
|
5224
|
+
}
|
|
5225
|
+
async run(plugin, command, config) {
|
|
5226
|
+
const task = config.userTask?.trim();
|
|
5227
|
+
if (!task) {
|
|
5228
|
+
throw new ConfigError("userTask is required");
|
|
5229
|
+
}
|
|
5230
|
+
const mode = normalizeOrchestrationMode(config.orchestrationMode);
|
|
5231
|
+
const converterModel = config.converterModel ? asModelId(String(config.converterModel)) : void 0;
|
|
5232
|
+
const converter = converterModel ? new PluginConverter(this.responses, this.http, this.auth, {
|
|
5233
|
+
converterModel
|
|
5234
|
+
}) : this.converter;
|
|
5235
|
+
let spec;
|
|
5236
|
+
if (mode === OrchestrationModes.Dynamic) {
|
|
5237
|
+
spec = await converter.toWorkflowDynamic(plugin, command, task);
|
|
5238
|
+
} else {
|
|
5239
|
+
spec = await converter.toWorkflow(plugin, command, task);
|
|
5240
|
+
}
|
|
5241
|
+
if (config.model) {
|
|
5242
|
+
spec = { ...spec, model: String(config.model) };
|
|
5243
|
+
}
|
|
5244
|
+
return this.runner.run(spec, config);
|
|
5245
|
+
}
|
|
5246
|
+
async quickRun(pluginUrl, command, userTask, config = {}) {
|
|
5247
|
+
const plugin = await this.load(pluginUrl);
|
|
5248
|
+
return this.run(plugin, command, { ...config, userTask });
|
|
5249
|
+
}
|
|
5250
|
+
};
|
|
5251
|
+
function normalizeOrchestrationMode(mode) {
|
|
5252
|
+
if (!mode) return OrchestrationModes.DAG;
|
|
5253
|
+
if (mode !== OrchestrationModes.DAG && mode !== OrchestrationModes.Dynamic) {
|
|
5254
|
+
throw new ConfigError(`invalid orchestration mode: ${mode}`);
|
|
5255
|
+
}
|
|
5256
|
+
return mode;
|
|
5257
|
+
}
|
|
5258
|
+
function resolveCommand(plugin, commandName) {
|
|
5259
|
+
const trimmed = commandName.trim();
|
|
5260
|
+
if (!trimmed) {
|
|
5261
|
+
throw new ConfigError("command is required");
|
|
5262
|
+
}
|
|
5263
|
+
const command = plugin.commands[trimmed];
|
|
5264
|
+
if (!command) {
|
|
5265
|
+
throw new ConfigError("unknown command");
|
|
5266
|
+
}
|
|
5267
|
+
return command;
|
|
5268
|
+
}
|
|
5269
|
+
var pluginToWorkflowSystemPrompt = `You convert a ModelRelay plugin (markdown files) into a single workflow JSON spec.
|
|
5270
|
+
|
|
5271
|
+
Rules:
|
|
5272
|
+
- Output MUST be a single JSON object and MUST validate as workflow.
|
|
5273
|
+
- Do NOT output markdown, commentary, or code fences.
|
|
5274
|
+
- Use a DAG with parallelism when multiple agents are independent.
|
|
5275
|
+
- Use join.all to aggregate parallel branches and then a final synthesizer node.
|
|
5276
|
+
- Use depends_on for edges between nodes.
|
|
5277
|
+
- Bind node outputs using {{placeholders}} when passing data forward.
|
|
5278
|
+
- Tool contract:
|
|
5279
|
+
- Target tools.v0 client tools (see docs/reference/tools.md).
|
|
5280
|
+
- Workspace access MUST use these exact function tool names:
|
|
5281
|
+
- ${Object.values(PluginToolNames).join(", ")}
|
|
5282
|
+
- Prefer fs_* tools for reading/listing/searching the workspace (use bash only when necessary).
|
|
5283
|
+
- Do NOT invent ad-hoc tool names (no repo.*, github.*, filesystem.*, etc.).
|
|
5284
|
+
- All client tools MUST be represented as type="function" tools.
|
|
5285
|
+
- Any node that includes tools MUST set tool_execution.mode="client".
|
|
5286
|
+
- Prefer minimal nodes needed to satisfy the task.
|
|
5287
|
+
`;
|
|
5288
|
+
var pluginOrchestrationSystemPrompt = `You plan which plugin agents to run based only on their descriptions.
|
|
5289
|
+
|
|
5290
|
+
Rules:
|
|
5291
|
+
- Output MUST be a single JSON object that matches orchestration.plan.v1.
|
|
5292
|
+
- Do NOT output markdown, commentary, or code fences.
|
|
5293
|
+
- Select only from the provided agent IDs.
|
|
5294
|
+
- Prefer minimal agents needed to satisfy the user task.
|
|
5295
|
+
- Use multiple steps only when later agents must build on earlier results.
|
|
5296
|
+
- Each step can run agents in parallel.
|
|
5297
|
+
- Use "id" + "depends_on" if you need non-sequential step ordering.
|
|
5298
|
+
`;
|
|
5299
|
+
function buildPluginConversionPrompt(plugin, command, userTask) {
|
|
5300
|
+
const out = [];
|
|
5301
|
+
out.push(`PLUGIN_URL: ${plugin.url}`);
|
|
5302
|
+
out.push(`COMMAND: ${command.name}`);
|
|
5303
|
+
out.push("USER_TASK:");
|
|
5304
|
+
out.push(userTask.trim());
|
|
5305
|
+
out.push("");
|
|
5306
|
+
out.push(`PLUGIN_MANIFEST:`);
|
|
5307
|
+
out.push(JSON.stringify(plugin.manifest));
|
|
5308
|
+
out.push("");
|
|
5309
|
+
out.push(`COMMAND_MARKDOWN (commands/${command.name}.md):`);
|
|
5310
|
+
out.push(command.prompt);
|
|
5311
|
+
out.push("");
|
|
5312
|
+
const agentNames = Object.keys(plugin.agents).sort();
|
|
5313
|
+
if (agentNames.length) {
|
|
5314
|
+
out.push("AGENTS_MARKDOWN:");
|
|
5315
|
+
for (const name of agentNames) {
|
|
5316
|
+
out.push(`---- agents/${name}.md ----`);
|
|
5317
|
+
out.push(plugin.agents[name].systemPrompt);
|
|
5318
|
+
out.push("");
|
|
5319
|
+
}
|
|
5320
|
+
}
|
|
5321
|
+
return out.join("\n");
|
|
5322
|
+
}
|
|
5323
|
+
function buildOrchestrationCandidates(plugin, command) {
|
|
5324
|
+
const names = command.agentRefs?.length ? command.agentRefs : Object.values(plugin.agents).map((agent) => agent.name);
|
|
5325
|
+
if (!names.length) {
|
|
5326
|
+
throw new PluginOrchestrationError(
|
|
5327
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5328
|
+
"no agents available for dynamic orchestration"
|
|
5329
|
+
);
|
|
5330
|
+
}
|
|
5331
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
5332
|
+
const candidates = [];
|
|
5333
|
+
for (const name of names) {
|
|
5334
|
+
const agent = plugin.agents[String(name)];
|
|
5335
|
+
if (!agent) {
|
|
5336
|
+
throw new PluginOrchestrationError(
|
|
5337
|
+
PluginOrchestrationErrorCodes.UnknownAgent,
|
|
5338
|
+
`agent "${name}" not found`
|
|
5339
|
+
);
|
|
5340
|
+
}
|
|
5341
|
+
const desc = agent.description?.trim();
|
|
5342
|
+
if (!desc) {
|
|
5343
|
+
throw new PluginOrchestrationError(
|
|
5344
|
+
PluginOrchestrationErrorCodes.MissingDescription,
|
|
5345
|
+
`agent "${name}" missing description`
|
|
5346
|
+
);
|
|
5347
|
+
}
|
|
5348
|
+
lookup.set(String(name), agent);
|
|
5349
|
+
candidates.push({ name, description: desc, agent });
|
|
5350
|
+
}
|
|
5351
|
+
return { candidates, lookup };
|
|
5352
|
+
}
|
|
5353
|
+
function buildPluginOrchestrationPrompt(plugin, command, userTask, candidates) {
|
|
5354
|
+
const out = [];
|
|
5355
|
+
if (plugin.manifest.name) {
|
|
5356
|
+
out.push(`PLUGIN_NAME: ${plugin.manifest.name}`);
|
|
5357
|
+
}
|
|
5358
|
+
if (plugin.manifest.description) {
|
|
5359
|
+
out.push(`PLUGIN_DESCRIPTION: ${plugin.manifest.description}`);
|
|
5360
|
+
}
|
|
5361
|
+
out.push(`COMMAND: ${command.name}`);
|
|
5362
|
+
out.push("USER_TASK:");
|
|
5363
|
+
out.push(userTask.trim());
|
|
5364
|
+
out.push("");
|
|
5365
|
+
if (command.prompt.trim()) {
|
|
5366
|
+
out.push("COMMAND_MARKDOWN:");
|
|
5367
|
+
out.push(command.prompt);
|
|
5368
|
+
out.push("");
|
|
5369
|
+
}
|
|
5370
|
+
out.push("CANDIDATE_AGENTS:");
|
|
5371
|
+
for (const c of candidates) {
|
|
5372
|
+
out.push(`- id: ${c.name}`);
|
|
5373
|
+
out.push(` description: ${c.description}`);
|
|
5374
|
+
}
|
|
5375
|
+
return out.join("\n");
|
|
5376
|
+
}
|
|
5377
|
+
function validateOrchestrationPlan(plan, lookup) {
|
|
5378
|
+
if (plan.max_parallelism && plan.max_parallelism < 1) {
|
|
5379
|
+
throw new PluginOrchestrationError(
|
|
5380
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5381
|
+
"max_parallelism must be >= 1"
|
|
5382
|
+
);
|
|
5383
|
+
}
|
|
5384
|
+
const stepIds = /* @__PURE__ */ new Map();
|
|
5385
|
+
let hasExplicitDeps = false;
|
|
5386
|
+
plan.steps.forEach((step, idx) => {
|
|
5387
|
+
if (step.depends_on?.length) {
|
|
5388
|
+
hasExplicitDeps = true;
|
|
5389
|
+
}
|
|
5390
|
+
if (step.id) {
|
|
5391
|
+
const key = step.id.trim();
|
|
5392
|
+
if (stepIds.has(key)) {
|
|
5393
|
+
throw new PluginOrchestrationError(
|
|
5394
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5395
|
+
`duplicate step id "${key}"`
|
|
5396
|
+
);
|
|
5397
|
+
}
|
|
5398
|
+
stepIds.set(key, idx);
|
|
5399
|
+
}
|
|
5400
|
+
});
|
|
5401
|
+
if (hasExplicitDeps) {
|
|
5402
|
+
plan.steps.forEach((step) => {
|
|
5403
|
+
if (!step.id?.trim()) {
|
|
5404
|
+
throw new PluginOrchestrationError(
|
|
5405
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5406
|
+
"step id required when depends_on is used"
|
|
5407
|
+
);
|
|
5408
|
+
}
|
|
5409
|
+
});
|
|
5410
|
+
}
|
|
5411
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5412
|
+
plan.steps.forEach((step, idx) => {
|
|
5413
|
+
if (!step.agents.length) {
|
|
5414
|
+
throw new PluginOrchestrationError(
|
|
5415
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5416
|
+
`step ${idx + 1} must include at least one agent`
|
|
5417
|
+
);
|
|
5418
|
+
}
|
|
5419
|
+
if (step.depends_on) {
|
|
5420
|
+
for (const dep of step.depends_on) {
|
|
5421
|
+
const depId = dep.trim();
|
|
5422
|
+
const depIndex = depId ? stepIds.get(depId) : void 0;
|
|
5423
|
+
if (!depId) {
|
|
5424
|
+
throw new PluginOrchestrationError(
|
|
5425
|
+
PluginOrchestrationErrorCodes.InvalidDependency,
|
|
5426
|
+
`step ${idx + 1} has empty depends_on`
|
|
5427
|
+
);
|
|
5428
|
+
}
|
|
5429
|
+
if (depIndex === void 0) {
|
|
5430
|
+
throw new PluginOrchestrationError(
|
|
5431
|
+
PluginOrchestrationErrorCodes.InvalidDependency,
|
|
5432
|
+
`step ${idx + 1} depends on unknown step "${depId}"`
|
|
5433
|
+
);
|
|
5434
|
+
}
|
|
5435
|
+
if (depIndex >= idx) {
|
|
5436
|
+
throw new PluginOrchestrationError(
|
|
5437
|
+
PluginOrchestrationErrorCodes.InvalidDependency,
|
|
5438
|
+
`step ${idx + 1} depends on future step "${depId}"`
|
|
5439
|
+
);
|
|
5440
|
+
}
|
|
5441
|
+
}
|
|
5442
|
+
}
|
|
5443
|
+
for (const agent of step.agents) {
|
|
5444
|
+
const id = agent.id.trim();
|
|
5445
|
+
if (!id) {
|
|
5446
|
+
throw new PluginOrchestrationError(
|
|
5447
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5448
|
+
`step ${idx + 1} agent id required`
|
|
5449
|
+
);
|
|
5450
|
+
}
|
|
5451
|
+
if (!lookup.has(id)) {
|
|
5452
|
+
throw new PluginOrchestrationError(
|
|
5453
|
+
PluginOrchestrationErrorCodes.UnknownAgent,
|
|
5454
|
+
`unknown agent "${id}"`
|
|
5455
|
+
);
|
|
5456
|
+
}
|
|
5457
|
+
if (!agent.reason?.trim()) {
|
|
5458
|
+
throw new PluginOrchestrationError(
|
|
5459
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5460
|
+
`agent "${id}" must include a reason`
|
|
5461
|
+
);
|
|
5462
|
+
}
|
|
5463
|
+
if (seen.has(id)) {
|
|
5464
|
+
throw new PluginOrchestrationError(
|
|
5465
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5466
|
+
`agent "${id}" referenced more than once`
|
|
5467
|
+
);
|
|
5468
|
+
}
|
|
5469
|
+
seen.add(id);
|
|
5470
|
+
}
|
|
5471
|
+
});
|
|
5472
|
+
}
|
|
5473
|
+
function buildDynamicWorkflowFromPlan(plugin, command, userTask, plan, lookup, model) {
|
|
5474
|
+
const stepKeys = plan.steps.map((step, idx) => step.id?.trim() || `step_${idx + 1}`);
|
|
5475
|
+
const stepOrder = new Map(stepKeys.map((key, idx) => [key, idx]));
|
|
5476
|
+
const stepOutputs = /* @__PURE__ */ new Map();
|
|
5477
|
+
const usedNodeIds = /* @__PURE__ */ new Set();
|
|
5478
|
+
const nodes = [];
|
|
5479
|
+
const hasExplicitDeps = plan.steps.some((step) => (step.depends_on?.length ?? 0) > 0);
|
|
5480
|
+
for (let i = 0; i < plan.steps.length; i += 1) {
|
|
5481
|
+
const step = plan.steps[i];
|
|
5482
|
+
const stepKey = stepKeys[i];
|
|
5483
|
+
const dependencyKeys = hasExplicitDeps ? step.depends_on || [] : i > 0 ? [stepKeys[i - 1]] : [];
|
|
5484
|
+
const deps = dependencyKeys.map((raw) => {
|
|
5485
|
+
const key = raw.trim();
|
|
5486
|
+
const nodeId = stepOutputs.get(key);
|
|
5487
|
+
if (!nodeId) {
|
|
5488
|
+
throw new PluginOrchestrationError(
|
|
5489
|
+
PluginOrchestrationErrorCodes.InvalidDependency,
|
|
5490
|
+
`missing output for dependency "${key}"`
|
|
5491
|
+
);
|
|
5492
|
+
}
|
|
5493
|
+
const depIndex = stepOrder.get(key);
|
|
5494
|
+
if (depIndex === void 0 || depIndex >= i) {
|
|
5495
|
+
throw new PluginOrchestrationError(
|
|
5496
|
+
PluginOrchestrationErrorCodes.InvalidDependency,
|
|
5497
|
+
`invalid dependency "${key}"`
|
|
5498
|
+
);
|
|
5499
|
+
}
|
|
5500
|
+
return { stepId: key, nodeId };
|
|
5501
|
+
});
|
|
5502
|
+
const stepNodeIds = [];
|
|
5503
|
+
for (const selection of step.agents) {
|
|
5504
|
+
const agentName = selection.id.trim();
|
|
5505
|
+
const agent = lookup.get(agentName);
|
|
5506
|
+
if (!agent) {
|
|
5507
|
+
throw new PluginOrchestrationError(
|
|
5508
|
+
PluginOrchestrationErrorCodes.UnknownAgent,
|
|
5509
|
+
`unknown agent "${agentName}"`
|
|
5510
|
+
);
|
|
5511
|
+
}
|
|
5512
|
+
const nodeId = parseNodeId(formatAgentNodeId(agentName));
|
|
5513
|
+
if (usedNodeIds.has(nodeId)) {
|
|
5514
|
+
throw new PluginOrchestrationError(
|
|
5515
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5516
|
+
`duplicate node id "${nodeId}"`
|
|
5517
|
+
);
|
|
5518
|
+
}
|
|
5519
|
+
const tools = buildToolRefs(agent, command);
|
|
5520
|
+
const node = {
|
|
5521
|
+
id: nodeId,
|
|
5522
|
+
type: WorkflowNodeTypesIntent.LLM,
|
|
5523
|
+
system: agent.systemPrompt.trim() || void 0,
|
|
5524
|
+
user: buildDynamicAgentUserPrompt(command, userTask, deps),
|
|
5525
|
+
depends_on: deps.length ? deps.map((d) => d.nodeId) : void 0,
|
|
5526
|
+
tools: tools.length ? tools : void 0,
|
|
5527
|
+
tool_execution: tools.length ? { mode: "client" } : void 0
|
|
5528
|
+
};
|
|
5529
|
+
nodes.push(node);
|
|
5530
|
+
stepNodeIds.push(nodeId);
|
|
5531
|
+
usedNodeIds.add(nodeId);
|
|
5532
|
+
}
|
|
5533
|
+
let outputNodeId = stepNodeIds[0];
|
|
5534
|
+
if (stepNodeIds.length > 1) {
|
|
5535
|
+
const joinId = parseNodeId(formatStepJoinNodeId(stepKey));
|
|
5536
|
+
if (usedNodeIds.has(joinId)) {
|
|
5537
|
+
throw new PluginOrchestrationError(
|
|
5538
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5539
|
+
`duplicate node id "${joinId}"`
|
|
5540
|
+
);
|
|
5541
|
+
}
|
|
5542
|
+
nodes.push({
|
|
5543
|
+
id: joinId,
|
|
5544
|
+
type: WorkflowNodeTypesIntent.JoinAll,
|
|
5545
|
+
depends_on: stepNodeIds
|
|
5546
|
+
});
|
|
5547
|
+
usedNodeIds.add(joinId);
|
|
5548
|
+
outputNodeId = joinId;
|
|
5549
|
+
}
|
|
5550
|
+
stepOutputs.set(stepKey, outputNodeId);
|
|
5551
|
+
}
|
|
5552
|
+
const terminalOutputs = findTerminalOutputs(stepKeys, plan, stepOutputs, hasExplicitDeps);
|
|
5553
|
+
const synthId = parseNodeId("orchestrator_synthesize");
|
|
5554
|
+
const synthNode = {
|
|
5555
|
+
id: synthId,
|
|
5556
|
+
type: WorkflowNodeTypesIntent.LLM,
|
|
5557
|
+
user: buildDynamicSynthesisPrompt(command, userTask, terminalOutputs),
|
|
5558
|
+
depends_on: terminalOutputs
|
|
5559
|
+
};
|
|
5560
|
+
const spec = {
|
|
5561
|
+
kind: WorkflowKinds.WorkflowIntent,
|
|
5562
|
+
name: plugin.manifest.name?.trim() || command.name,
|
|
5563
|
+
model: String(model),
|
|
5564
|
+
max_parallelism: plan.max_parallelism,
|
|
5565
|
+
nodes: [...nodes, synthNode],
|
|
5566
|
+
outputs: [{ name: parseOutputName("result"), from: synthId }]
|
|
5567
|
+
};
|
|
5568
|
+
return spec;
|
|
5569
|
+
}
|
|
5570
|
+
function buildDynamicAgentUserPrompt(command, task, deps) {
|
|
5571
|
+
const parts = [];
|
|
5572
|
+
if (command.prompt.trim()) {
|
|
5573
|
+
parts.push(command.prompt.trim());
|
|
5574
|
+
}
|
|
5575
|
+
parts.push("USER_TASK:");
|
|
5576
|
+
parts.push(task.trim());
|
|
5577
|
+
if (deps.length) {
|
|
5578
|
+
parts.push("", "PREVIOUS_STEP_OUTPUTS:");
|
|
5579
|
+
for (const dep of deps) {
|
|
5580
|
+
parts.push(`- ${dep.stepId}: {{${dep.nodeId}}}`);
|
|
5581
|
+
}
|
|
3880
5582
|
}
|
|
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;
|
|
5583
|
+
return parts.join("\n");
|
|
3889
5584
|
}
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
this.hasAccessToken = !!cfg.accessToken?.trim();
|
|
5585
|
+
function buildDynamicSynthesisPrompt(command, task, outputs) {
|
|
5586
|
+
const parts = ["Synthesize the results and complete the task."];
|
|
5587
|
+
if (command.prompt.trim()) {
|
|
5588
|
+
parts.push("", "COMMAND:");
|
|
5589
|
+
parts.push(command.prompt.trim());
|
|
3896
5590
|
}
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
5591
|
+
parts.push("", "USER_TASK:");
|
|
5592
|
+
parts.push(task.trim());
|
|
5593
|
+
if (outputs.length) {
|
|
5594
|
+
parts.push("", "RESULTS:");
|
|
5595
|
+
for (const id of outputs) {
|
|
5596
|
+
parts.push(`- {{${id}}}`);
|
|
3902
5597
|
}
|
|
3903
5598
|
}
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
5599
|
+
return parts.join("\n");
|
|
5600
|
+
}
|
|
5601
|
+
function buildToolRefs(agent, command) {
|
|
5602
|
+
const names = agent.tools?.length ? agent.tools : command.tools?.length ? command.tools : defaultDynamicToolNames;
|
|
5603
|
+
const unique = /* @__PURE__ */ new Set();
|
|
5604
|
+
for (const name of names) {
|
|
5605
|
+
if (!allowedToolSet.has(name)) {
|
|
5606
|
+
throw new PluginOrchestrationError(
|
|
5607
|
+
PluginOrchestrationErrorCodes.UnknownTool,
|
|
5608
|
+
`unknown tool "${name}"`
|
|
3908
5609
|
);
|
|
3909
5610
|
}
|
|
5611
|
+
unique.add(name);
|
|
3910
5612
|
}
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
const
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
5613
|
+
return Array.from(unique.values());
|
|
5614
|
+
}
|
|
5615
|
+
function findTerminalOutputs(stepKeys, plan, outputs, explicit) {
|
|
5616
|
+
if (!explicit) {
|
|
5617
|
+
const lastKey = stepKeys[stepKeys.length - 1];
|
|
5618
|
+
const out = outputs.get(lastKey);
|
|
5619
|
+
return out ? [out] : [];
|
|
5620
|
+
}
|
|
5621
|
+
const depended = /* @__PURE__ */ new Set();
|
|
5622
|
+
plan.steps.forEach((step) => {
|
|
5623
|
+
(step.depends_on || []).forEach((dep) => depended.add(dep.trim()));
|
|
5624
|
+
});
|
|
5625
|
+
return stepKeys.filter((key) => !depended.has(key)).map((key) => outputs.get(key)).filter((value) => Boolean(value));
|
|
5626
|
+
}
|
|
5627
|
+
function formatAgentNodeId(name) {
|
|
5628
|
+
const token = sanitizeNodeToken(name);
|
|
5629
|
+
if (!token) {
|
|
5630
|
+
throw new PluginOrchestrationError(
|
|
5631
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5632
|
+
"agent id must contain alphanumeric characters"
|
|
5633
|
+
);
|
|
3921
5634
|
}
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
5635
|
+
return `agent_${token}`;
|
|
5636
|
+
}
|
|
5637
|
+
function formatStepJoinNodeId(stepKey) {
|
|
5638
|
+
const token = sanitizeNodeToken(stepKey);
|
|
5639
|
+
if (!token) {
|
|
5640
|
+
throw new PluginOrchestrationError(
|
|
5641
|
+
PluginOrchestrationErrorCodes.InvalidPlan,
|
|
5642
|
+
"step id must contain alphanumeric characters"
|
|
5643
|
+
);
|
|
5644
|
+
}
|
|
5645
|
+
return token.startsWith("step_") ? `${token}_join` : `step_${token}_join`;
|
|
5646
|
+
}
|
|
5647
|
+
function sanitizeNodeToken(raw) {
|
|
5648
|
+
const trimmed = raw.trim();
|
|
5649
|
+
if (!trimmed) return "";
|
|
5650
|
+
let out = "";
|
|
5651
|
+
for (const ch of trimmed) {
|
|
5652
|
+
if (/[a-zA-Z0-9]/.test(ch)) {
|
|
5653
|
+
out += ch.toLowerCase();
|
|
5654
|
+
} else {
|
|
5655
|
+
out += "_";
|
|
3929
5656
|
}
|
|
3930
|
-
|
|
5657
|
+
}
|
|
5658
|
+
out = out.replace(/_+/g, "_");
|
|
5659
|
+
return out.replace(/^_+|_+$/g, "");
|
|
5660
|
+
}
|
|
5661
|
+
async function ensureModelSupportsTools(http, auth, model) {
|
|
5662
|
+
const authHeaders = await auth.authForResponses();
|
|
5663
|
+
const query = encodeURIComponent("tools");
|
|
5664
|
+
const response = await http.json(
|
|
5665
|
+
`/models?capability=${query}`,
|
|
5666
|
+
{
|
|
3931
5667
|
method: "GET",
|
|
3932
|
-
apiKey:
|
|
3933
|
-
|
|
3934
|
-
|
|
5668
|
+
apiKey: authHeaders.apiKey,
|
|
5669
|
+
accessToken: authHeaders.accessToken
|
|
5670
|
+
}
|
|
5671
|
+
);
|
|
5672
|
+
const modelId = String(model).trim();
|
|
5673
|
+
const found = response.models?.some((entry) => entry.model_id?.trim() === modelId);
|
|
5674
|
+
if (!found) {
|
|
5675
|
+
throw new PluginOrchestrationError(
|
|
5676
|
+
PluginOrchestrationErrorCodes.InvalidToolConfig,
|
|
5677
|
+
`model "${modelId}" does not support tool calling`
|
|
5678
|
+
);
|
|
3935
5679
|
}
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
5680
|
+
}
|
|
5681
|
+
function normalizeWorkflowIntent(spec) {
|
|
5682
|
+
const validation = validateWithZod(workflowIntentSchema, spec);
|
|
5683
|
+
if (!validation.success) {
|
|
5684
|
+
throw new ConfigError("workflow intent validation failed");
|
|
5685
|
+
}
|
|
5686
|
+
validateWorkflowTools(spec);
|
|
5687
|
+
return spec;
|
|
5688
|
+
}
|
|
5689
|
+
function validateWorkflowTools(spec) {
|
|
5690
|
+
for (const node of spec.nodes) {
|
|
5691
|
+
if (node.type !== WorkflowNodeTypesIntent.LLM || !node.tools?.length) {
|
|
5692
|
+
continue;
|
|
5693
|
+
}
|
|
5694
|
+
const tools = node.tools || [];
|
|
5695
|
+
let mode = node.tool_execution?.mode;
|
|
5696
|
+
for (const tool of tools) {
|
|
5697
|
+
if (typeof tool !== "string") {
|
|
5698
|
+
throw new ConfigError(`plugin conversion only supports tools.v0 function tools`);
|
|
5699
|
+
}
|
|
5700
|
+
if (!allowedToolSet.has(tool)) {
|
|
5701
|
+
throw new ConfigError(`unsupported tool "${tool}" (plugin conversion targets tools.v0)`);
|
|
5702
|
+
}
|
|
5703
|
+
mode = "client";
|
|
3954
5704
|
}
|
|
3955
|
-
if (
|
|
3956
|
-
throw new ConfigError("
|
|
5705
|
+
if (mode && mode !== "client") {
|
|
5706
|
+
throw new ConfigError(`tool_execution.mode must be "client" for plugin conversion`);
|
|
3957
5707
|
}
|
|
3958
|
-
|
|
3959
|
-
|
|
5708
|
+
node.tool_execution = { mode: "client" };
|
|
5709
|
+
}
|
|
5710
|
+
}
|
|
5711
|
+
function parsePluginManifest(markdown) {
|
|
5712
|
+
const trimmed = markdown.trim();
|
|
5713
|
+
if (!trimmed) return {};
|
|
5714
|
+
const frontMatter = parseManifestFrontMatter(trimmed);
|
|
5715
|
+
if (frontMatter) return frontMatter;
|
|
5716
|
+
const lines = splitLines(trimmed);
|
|
5717
|
+
let name = "";
|
|
5718
|
+
for (const line of lines) {
|
|
5719
|
+
if (line.startsWith("# ")) {
|
|
5720
|
+
name = line.slice(2).trim();
|
|
5721
|
+
break;
|
|
3960
5722
|
}
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
5723
|
+
}
|
|
5724
|
+
let description = "";
|
|
5725
|
+
if (name) {
|
|
5726
|
+
let after = false;
|
|
5727
|
+
for (const line of lines) {
|
|
5728
|
+
const trimmedLine = line.trim();
|
|
5729
|
+
if (trimmedLine.startsWith("# ")) {
|
|
5730
|
+
after = true;
|
|
5731
|
+
continue;
|
|
5732
|
+
}
|
|
5733
|
+
if (!after) continue;
|
|
5734
|
+
if (!trimmedLine) continue;
|
|
5735
|
+
if (trimmedLine.startsWith("## ")) break;
|
|
5736
|
+
description = trimmedLine;
|
|
5737
|
+
break;
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5740
|
+
return { name, description };
|
|
5741
|
+
}
|
|
5742
|
+
function parseManifestFrontMatter(markdown) {
|
|
5743
|
+
const lines = splitLines(markdown);
|
|
5744
|
+
if (!lines.length || lines[0].trim() !== "---") return null;
|
|
5745
|
+
const end = lines.findIndex((line, idx) => idx > 0 && line.trim() === "---");
|
|
5746
|
+
if (end === -1) return null;
|
|
5747
|
+
const manifest = {};
|
|
5748
|
+
let currentList = null;
|
|
5749
|
+
for (const line of lines.slice(1, end)) {
|
|
5750
|
+
const raw = line.trim();
|
|
5751
|
+
if (!raw || raw.startsWith("#")) continue;
|
|
5752
|
+
if (raw.startsWith("- ") && currentList) {
|
|
5753
|
+
const item = raw.slice(2).trim();
|
|
5754
|
+
if (item) {
|
|
5755
|
+
if (currentList === "commands") {
|
|
5756
|
+
manifest.commands = [...manifest.commands || [], asPluginCommandName(item)];
|
|
5757
|
+
} else {
|
|
5758
|
+
manifest.agents = [...manifest.agents || [], asPluginAgentName(item)];
|
|
5759
|
+
}
|
|
3967
5760
|
}
|
|
5761
|
+
continue;
|
|
5762
|
+
}
|
|
5763
|
+
currentList = null;
|
|
5764
|
+
const [keyRaw, ...rest] = raw.split(":");
|
|
5765
|
+
if (!keyRaw || rest.length === 0) continue;
|
|
5766
|
+
const key = keyRaw.trim().toLowerCase();
|
|
5767
|
+
const val = rest.join(":").trim().replace(/^['"]|['"]$/g, "");
|
|
5768
|
+
if (key === "name") manifest.name = val;
|
|
5769
|
+
if (key === "description") manifest.description = val;
|
|
5770
|
+
if (key === "version") manifest.version = val;
|
|
5771
|
+
if (key === "commands") currentList = "commands";
|
|
5772
|
+
if (key === "agents") currentList = "agents";
|
|
5773
|
+
}
|
|
5774
|
+
manifest.commands = manifest.commands?.sort();
|
|
5775
|
+
manifest.agents = manifest.agents?.sort();
|
|
5776
|
+
return manifest;
|
|
5777
|
+
}
|
|
5778
|
+
function parseMarkdownFrontMatter(markdown) {
|
|
5779
|
+
const trimmed = markdown.trim();
|
|
5780
|
+
if (!trimmed.startsWith("---")) {
|
|
5781
|
+
return { body: markdown };
|
|
5782
|
+
}
|
|
5783
|
+
const lines = splitLines(trimmed);
|
|
5784
|
+
const endIdx = lines.findIndex((line, idx) => idx > 0 && line.trim() === "---");
|
|
5785
|
+
if (endIdx === -1) {
|
|
5786
|
+
return { body: markdown };
|
|
5787
|
+
}
|
|
5788
|
+
let description;
|
|
5789
|
+
let tools;
|
|
5790
|
+
let currentList = null;
|
|
5791
|
+
const toolItems = [];
|
|
5792
|
+
for (const line of lines.slice(1, endIdx)) {
|
|
5793
|
+
const raw = line.trim();
|
|
5794
|
+
if (!raw || raw.startsWith("#")) continue;
|
|
5795
|
+
if (raw.startsWith("- ") && currentList === "tools") {
|
|
5796
|
+
const item = raw.slice(2).trim();
|
|
5797
|
+
if (item) toolItems.push(item);
|
|
5798
|
+
continue;
|
|
5799
|
+
}
|
|
5800
|
+
currentList = null;
|
|
5801
|
+
const [keyRaw, ...rest] = raw.split(":");
|
|
5802
|
+
if (!keyRaw || rest.length === 0) continue;
|
|
5803
|
+
const key = keyRaw.trim().toLowerCase();
|
|
5804
|
+
const val = rest.join(":").trim().replace(/^['"]|['"]$/g, "");
|
|
5805
|
+
if (key === "description") description = val;
|
|
5806
|
+
if (key === "tools") {
|
|
5807
|
+
if (!val) {
|
|
5808
|
+
currentList = "tools";
|
|
5809
|
+
continue;
|
|
5810
|
+
}
|
|
5811
|
+
toolItems.push(...splitFrontMatterList(val));
|
|
5812
|
+
}
|
|
5813
|
+
}
|
|
5814
|
+
if (toolItems.length) {
|
|
5815
|
+
tools = toolItems.map((item) => parseToolName(item));
|
|
5816
|
+
}
|
|
5817
|
+
const body = lines.slice(endIdx + 1).join("\n").replace(/^[\n\r]+/, "");
|
|
5818
|
+
return { description, tools, body };
|
|
5819
|
+
}
|
|
5820
|
+
function parseToolName(raw) {
|
|
5821
|
+
const val = raw.trim();
|
|
5822
|
+
if (!allowedToolSet.has(val)) {
|
|
5823
|
+
throw new PluginOrchestrationError(
|
|
5824
|
+
PluginOrchestrationErrorCodes.UnknownTool,
|
|
5825
|
+
`unknown tool "${raw}"`
|
|
3968
5826
|
);
|
|
3969
5827
|
}
|
|
3970
|
-
|
|
5828
|
+
return val;
|
|
5829
|
+
}
|
|
5830
|
+
function splitFrontMatterList(raw) {
|
|
5831
|
+
const cleaned = raw.trim().replace(/^\[/, "").replace(/\]$/, "");
|
|
5832
|
+
if (!cleaned) return [];
|
|
5833
|
+
return cleaned.split(",").map((part) => part.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
|
|
5834
|
+
}
|
|
5835
|
+
function extractAgentRefs(markdown) {
|
|
5836
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5837
|
+
const out = [];
|
|
5838
|
+
const lines = splitLines(markdown);
|
|
5839
|
+
for (const line of lines) {
|
|
5840
|
+
const lower = line.toLowerCase();
|
|
5841
|
+
const idx = lower.indexOf("agents/");
|
|
5842
|
+
if (idx === -1) continue;
|
|
5843
|
+
if (!lower.includes(".md", idx)) continue;
|
|
5844
|
+
let seg = line.slice(idx).trim();
|
|
5845
|
+
seg = seg.replace(/^agents\//, "");
|
|
5846
|
+
seg = seg.split(".md")[0];
|
|
5847
|
+
seg = seg.replace(/[`* _]/g, "").trim();
|
|
5848
|
+
if (!seg || seen.has(seg)) continue;
|
|
5849
|
+
seen.add(seg);
|
|
5850
|
+
out.push(asPluginAgentName(seg));
|
|
5851
|
+
}
|
|
5852
|
+
return out.sort();
|
|
5853
|
+
}
|
|
5854
|
+
function splitLines(input) {
|
|
5855
|
+
return input.replace(/\r\n/g, "\n").split("\n");
|
|
5856
|
+
}
|
|
5857
|
+
function basename(path) {
|
|
5858
|
+
const parts = path.split("/");
|
|
5859
|
+
const last = parts[parts.length - 1] || "";
|
|
5860
|
+
return last.replace(/\.md$/i, "");
|
|
5861
|
+
}
|
|
5862
|
+
function joinRepoPath(base, elem) {
|
|
5863
|
+
const clean = (value) => value.replace(/^\/+|\/+$/g, "");
|
|
5864
|
+
const b = clean(base || "");
|
|
5865
|
+
const e = clean(elem || "");
|
|
5866
|
+
if (!b) return e;
|
|
5867
|
+
if (!e) return b;
|
|
5868
|
+
return `${b}/${e}`;
|
|
5869
|
+
}
|
|
5870
|
+
function sortedKeys(items) {
|
|
5871
|
+
return items.slice().sort();
|
|
5872
|
+
}
|
|
5873
|
+
function clonePlugin(plugin) {
|
|
5874
|
+
return {
|
|
5875
|
+
...plugin,
|
|
5876
|
+
manifest: { ...plugin.manifest },
|
|
5877
|
+
commands: { ...plugin.commands },
|
|
5878
|
+
agents: { ...plugin.agents },
|
|
5879
|
+
rawFiles: { ...plugin.rawFiles },
|
|
5880
|
+
loadedAt: new Date(plugin.loadedAt)
|
|
5881
|
+
};
|
|
5882
|
+
}
|
|
5883
|
+
function asPluginId(value) {
|
|
5884
|
+
const trimmed = value.trim();
|
|
5885
|
+
if (!trimmed) throw new ConfigError("plugin id required");
|
|
5886
|
+
return trimmed;
|
|
5887
|
+
}
|
|
5888
|
+
function asPluginUrl(value) {
|
|
5889
|
+
const trimmed = value.trim();
|
|
5890
|
+
if (!trimmed) throw new ConfigError("plugin url required");
|
|
5891
|
+
return trimmed;
|
|
5892
|
+
}
|
|
5893
|
+
function asPluginCommandName(value) {
|
|
5894
|
+
const trimmed = value.trim();
|
|
5895
|
+
if (!trimmed) throw new ConfigError("plugin command name required");
|
|
5896
|
+
return trimmed;
|
|
5897
|
+
}
|
|
5898
|
+
function asPluginAgentName(value) {
|
|
5899
|
+
const trimmed = value.trim();
|
|
5900
|
+
if (!trimmed) throw new ConfigError("plugin agent name required");
|
|
5901
|
+
return trimmed;
|
|
5902
|
+
}
|
|
5903
|
+
function parseGitHubPluginRef(raw) {
|
|
5904
|
+
let url = raw.trim();
|
|
5905
|
+
if (!url) {
|
|
5906
|
+
throw new ConfigError("source url required");
|
|
5907
|
+
}
|
|
5908
|
+
if (url.startsWith("git@github.com:")) {
|
|
5909
|
+
url = `https://github.com/${url.replace("git@github.com:", "")}`;
|
|
5910
|
+
}
|
|
5911
|
+
if (!url.includes("://")) {
|
|
5912
|
+
url = `https://${url}`;
|
|
5913
|
+
}
|
|
5914
|
+
const parsed = new URL(url);
|
|
5915
|
+
const host = parsed.hostname.replace(/^www\./, "").toLowerCase();
|
|
5916
|
+
if (host !== "github.com" && host !== "raw.githubusercontent.com") {
|
|
5917
|
+
throw new ConfigError(`unsupported host: ${parsed.hostname}`);
|
|
5918
|
+
}
|
|
5919
|
+
let ref = parsed.searchParams.get("ref")?.trim() || "";
|
|
5920
|
+
const parts = parsed.pathname.split("/").filter(Boolean);
|
|
5921
|
+
if (parts.length < 2) {
|
|
5922
|
+
throw new ConfigError("invalid github url: expected /owner/repo");
|
|
5923
|
+
}
|
|
5924
|
+
const owner = parts[0];
|
|
5925
|
+
let repoPart = parts[1].replace(/\.git$/i, "");
|
|
5926
|
+
const atIdx = repoPart.indexOf("@");
|
|
5927
|
+
if (atIdx > 0 && atIdx < repoPart.length - 1) {
|
|
5928
|
+
if (!ref) {
|
|
5929
|
+
ref = repoPart.slice(atIdx + 1);
|
|
5930
|
+
}
|
|
5931
|
+
repoPart = repoPart.slice(0, atIdx);
|
|
5932
|
+
}
|
|
5933
|
+
const repo = repoPart;
|
|
5934
|
+
let rest = parts.slice(2);
|
|
5935
|
+
if (host === "github.com" && rest.length >= 2 && (rest[0] === "tree" || rest[0] === "blob")) {
|
|
5936
|
+
if (!ref) {
|
|
5937
|
+
ref = rest[1];
|
|
5938
|
+
}
|
|
5939
|
+
rest = rest.slice(2);
|
|
5940
|
+
}
|
|
5941
|
+
if (host === "raw.githubusercontent.com") {
|
|
5942
|
+
if (!rest.length) {
|
|
5943
|
+
throw new ConfigError("invalid raw github url");
|
|
5944
|
+
}
|
|
5945
|
+
if (!ref) {
|
|
5946
|
+
ref = rest[0];
|
|
5947
|
+
}
|
|
5948
|
+
rest = rest.slice(1);
|
|
5949
|
+
}
|
|
5950
|
+
let repoPath = rest.join("/");
|
|
5951
|
+
repoPath = repoPath.replace(/^\/+|\/+$/g, "");
|
|
5952
|
+
if (/plugin\.md$/i.test(repoPath) || /skill\.md$/i.test(repoPath)) {
|
|
5953
|
+
repoPath = repoPath.split("/").slice(0, -1).join("/");
|
|
5954
|
+
}
|
|
5955
|
+
if (/\.md$/i.test(repoPath)) {
|
|
5956
|
+
const commandsIdx = repoPath.indexOf("/commands/");
|
|
5957
|
+
if (commandsIdx >= 0) {
|
|
5958
|
+
repoPath = repoPath.slice(0, commandsIdx);
|
|
5959
|
+
}
|
|
5960
|
+
const agentsIdx = repoPath.indexOf("/agents/");
|
|
5961
|
+
if (agentsIdx >= 0) {
|
|
5962
|
+
repoPath = repoPath.slice(0, agentsIdx);
|
|
5963
|
+
}
|
|
5964
|
+
repoPath = repoPath.replace(/^\/+|\/+$/g, "");
|
|
5965
|
+
}
|
|
5966
|
+
if (!ref) {
|
|
5967
|
+
ref = DEFAULT_PLUGIN_REF;
|
|
5968
|
+
}
|
|
5969
|
+
const canonical = repoPath ? `github.com/${owner}/${repo}@${ref}/${repoPath}` : `github.com/${owner}/${repo}@${ref}`;
|
|
5970
|
+
return { owner, repo, ref, repoPath, canonical };
|
|
5971
|
+
}
|
|
5972
|
+
function specRequiresTools(spec) {
|
|
5973
|
+
for (const node of spec.nodes) {
|
|
5974
|
+
if (node.type === WorkflowNodeTypesIntent.LLM && node.tools?.length) {
|
|
5975
|
+
return true;
|
|
5976
|
+
}
|
|
5977
|
+
if (node.type === WorkflowNodeTypesIntent.MapFanout && node.subnode) {
|
|
5978
|
+
const sub = node.subnode;
|
|
5979
|
+
if (sub.tools?.length) {
|
|
5980
|
+
return true;
|
|
5981
|
+
}
|
|
5982
|
+
}
|
|
5983
|
+
}
|
|
5984
|
+
return false;
|
|
5985
|
+
}
|
|
3971
5986
|
|
|
3972
5987
|
// src/http.ts
|
|
3973
5988
|
var HTTPClient = class {
|
|
@@ -4349,29 +6364,184 @@ var CustomerResponsesClient = class {
|
|
|
4349
6364
|
mergeCustomerOptions(this.customerId, options)
|
|
4350
6365
|
);
|
|
4351
6366
|
}
|
|
4352
|
-
async text(system, user, options = {}) {
|
|
4353
|
-
return this.base.textForCustomer(
|
|
4354
|
-
this.customerId,
|
|
4355
|
-
system,
|
|
4356
|
-
user,
|
|
4357
|
-
mergeCustomerOptions(this.customerId, options)
|
|
4358
|
-
);
|
|
6367
|
+
async text(system, user, options = {}) {
|
|
6368
|
+
return this.base.textForCustomer(
|
|
6369
|
+
this.customerId,
|
|
6370
|
+
system,
|
|
6371
|
+
user,
|
|
6372
|
+
mergeCustomerOptions(this.customerId, options)
|
|
6373
|
+
);
|
|
6374
|
+
}
|
|
6375
|
+
async streamTextDeltas(system, user, options = {}) {
|
|
6376
|
+
return this.base.streamTextDeltasForCustomer(
|
|
6377
|
+
this.customerId,
|
|
6378
|
+
system,
|
|
6379
|
+
user,
|
|
6380
|
+
mergeCustomerOptions(this.customerId, options)
|
|
6381
|
+
);
|
|
6382
|
+
}
|
|
6383
|
+
};
|
|
6384
|
+
var CustomerScopedModelRelay = class {
|
|
6385
|
+
constructor(responses, customerId, baseUrl) {
|
|
6386
|
+
const normalized = normalizeCustomerId(customerId);
|
|
6387
|
+
this.responses = new CustomerResponsesClient(responses, normalized);
|
|
6388
|
+
this.customerId = normalized;
|
|
6389
|
+
this.baseUrl = baseUrl;
|
|
6390
|
+
}
|
|
6391
|
+
};
|
|
6392
|
+
|
|
6393
|
+
// src/tool_builder.ts
|
|
6394
|
+
function formatZodError(error) {
|
|
6395
|
+
if (error && typeof error === "object" && "issues" in error && Array.isArray(error.issues)) {
|
|
6396
|
+
const issues = error.issues;
|
|
6397
|
+
return issues.map((issue) => {
|
|
6398
|
+
const path = Array.isArray(issue.path) ? issue.path.join(".") : "";
|
|
6399
|
+
const msg = issue.message || "invalid";
|
|
6400
|
+
return path ? `${path}: ${msg}` : msg;
|
|
6401
|
+
}).join("; ");
|
|
6402
|
+
}
|
|
6403
|
+
return String(error);
|
|
6404
|
+
}
|
|
6405
|
+
var ToolBuilder = class {
|
|
6406
|
+
constructor() {
|
|
6407
|
+
this.entries = [];
|
|
6408
|
+
}
|
|
6409
|
+
/**
|
|
6410
|
+
* Add a tool with a Zod schema and handler.
|
|
6411
|
+
*
|
|
6412
|
+
* The handler receives parsed and validated arguments matching the schema.
|
|
6413
|
+
*
|
|
6414
|
+
* @param name - Tool name (must be unique)
|
|
6415
|
+
* @param description - Human-readable description of what the tool does
|
|
6416
|
+
* @param schema - Zod schema for the tool's parameters
|
|
6417
|
+
* @param handler - Function to execute when the tool is called
|
|
6418
|
+
* @returns this for chaining
|
|
6419
|
+
*
|
|
6420
|
+
* @example
|
|
6421
|
+
* ```typescript
|
|
6422
|
+
* tools.add(
|
|
6423
|
+
* "search_web",
|
|
6424
|
+
* "Search the web for information",
|
|
6425
|
+
* z.object({
|
|
6426
|
+
* query: z.string().describe("Search query"),
|
|
6427
|
+
* maxResults: z.number().optional().describe("Max results to return"),
|
|
6428
|
+
* }),
|
|
6429
|
+
* async (args) => {
|
|
6430
|
+
* // args is typed as { query: string; maxResults?: number }
|
|
6431
|
+
* return await searchAPI(args.query, args.maxResults);
|
|
6432
|
+
* }
|
|
6433
|
+
* );
|
|
6434
|
+
* ```
|
|
6435
|
+
*/
|
|
6436
|
+
add(name, description, schema, handler) {
|
|
6437
|
+
const tool = createFunctionToolFromSchema(name, description, schema);
|
|
6438
|
+
this.entries.push({
|
|
6439
|
+
name,
|
|
6440
|
+
description,
|
|
6441
|
+
schema,
|
|
6442
|
+
handler,
|
|
6443
|
+
tool
|
|
6444
|
+
});
|
|
6445
|
+
return this;
|
|
6446
|
+
}
|
|
6447
|
+
/**
|
|
6448
|
+
* Get tool definitions for use with ResponseBuilder.tools().
|
|
6449
|
+
*
|
|
6450
|
+
* @example
|
|
6451
|
+
* ```typescript
|
|
6452
|
+
* const response = await mr.responses.create(
|
|
6453
|
+
* mr.responses.new()
|
|
6454
|
+
* .model("claude-sonnet-4-5")
|
|
6455
|
+
* .tools(tools.definitions())
|
|
6456
|
+
* .user("What's the weather in Paris?")
|
|
6457
|
+
* .build()
|
|
6458
|
+
* );
|
|
6459
|
+
* ```
|
|
6460
|
+
*/
|
|
6461
|
+
definitions() {
|
|
6462
|
+
return this.entries.map((e) => e.tool);
|
|
6463
|
+
}
|
|
6464
|
+
/**
|
|
6465
|
+
* Get a ToolRegistry with all handlers registered.
|
|
6466
|
+
*
|
|
6467
|
+
* The handlers are wrapped to validate arguments against the schema
|
|
6468
|
+
* before invoking the user's handler. If validation fails, a
|
|
6469
|
+
* ToolArgsError is thrown (which ToolRegistry marks as retryable).
|
|
6470
|
+
*
|
|
6471
|
+
* Note: For mr.agent(), pass the ToolBuilder directly instead of calling
|
|
6472
|
+
* registry(). The agent method extracts both definitions and registry.
|
|
6473
|
+
*
|
|
6474
|
+
* @example
|
|
6475
|
+
* ```typescript
|
|
6476
|
+
* const registry = tools.registry();
|
|
6477
|
+
*
|
|
6478
|
+
* // Use with LocalSession (also pass definitions via defaultTools)
|
|
6479
|
+
* const session = mr.sessions.createLocal({
|
|
6480
|
+
* toolRegistry: registry,
|
|
6481
|
+
* defaultTools: tools.definitions(),
|
|
6482
|
+
* defaultModel: "claude-sonnet-4-5",
|
|
6483
|
+
* });
|
|
6484
|
+
* ```
|
|
6485
|
+
*/
|
|
6486
|
+
registry() {
|
|
6487
|
+
const reg = new ToolRegistry();
|
|
6488
|
+
for (const entry of this.entries) {
|
|
6489
|
+
const validatingHandler = async (args, call) => {
|
|
6490
|
+
const result = entry.schema.safeParse(args);
|
|
6491
|
+
if (!result.success) {
|
|
6492
|
+
throw new ToolArgsError(
|
|
6493
|
+
`Invalid arguments for tool '${entry.name}': ${formatZodError(result.error)}`,
|
|
6494
|
+
call.id,
|
|
6495
|
+
entry.name,
|
|
6496
|
+
call.function?.arguments ?? ""
|
|
6497
|
+
);
|
|
6498
|
+
}
|
|
6499
|
+
return entry.handler(result.data, call);
|
|
6500
|
+
};
|
|
6501
|
+
reg.register(entry.name, validatingHandler);
|
|
6502
|
+
}
|
|
6503
|
+
return reg;
|
|
6504
|
+
}
|
|
6505
|
+
/**
|
|
6506
|
+
* Get both definitions and registry.
|
|
6507
|
+
*
|
|
6508
|
+
* Useful when you need both for manual tool handling.
|
|
6509
|
+
*
|
|
6510
|
+
* @example
|
|
6511
|
+
* ```typescript
|
|
6512
|
+
* const { definitions, registry } = tools.build();
|
|
6513
|
+
*
|
|
6514
|
+
* const response = await mr.responses.create(
|
|
6515
|
+
* mr.responses.new()
|
|
6516
|
+
* .model("claude-sonnet-4-5")
|
|
6517
|
+
* .tools(definitions)
|
|
6518
|
+
* .user("What's the weather?")
|
|
6519
|
+
* .build()
|
|
6520
|
+
* );
|
|
6521
|
+
*
|
|
6522
|
+
* if (hasToolCalls(response)) {
|
|
6523
|
+
* const results = await registry.executeAll(response.output[0].toolCalls!);
|
|
6524
|
+
* // ...
|
|
6525
|
+
* }
|
|
6526
|
+
* ```
|
|
6527
|
+
*/
|
|
6528
|
+
build() {
|
|
6529
|
+
return {
|
|
6530
|
+
definitions: this.definitions(),
|
|
6531
|
+
registry: this.registry()
|
|
6532
|
+
};
|
|
4359
6533
|
}
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
mergeCustomerOptions(this.customerId, options)
|
|
4366
|
-
);
|
|
6534
|
+
/**
|
|
6535
|
+
* Get the number of tools defined.
|
|
6536
|
+
*/
|
|
6537
|
+
get size() {
|
|
6538
|
+
return this.entries.length;
|
|
4367
6539
|
}
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
this.
|
|
4373
|
-
this.customerId = normalized;
|
|
4374
|
-
this.baseUrl = baseUrl;
|
|
6540
|
+
/**
|
|
6541
|
+
* Check if a tool is defined.
|
|
6542
|
+
*/
|
|
6543
|
+
has(name) {
|
|
6544
|
+
return this.entries.some((e) => e.name === name);
|
|
4375
6545
|
}
|
|
4376
6546
|
};
|
|
4377
6547
|
|
|
@@ -4550,6 +6720,12 @@ var WorkflowIntentBuilder = class _WorkflowIntentBuilder {
|
|
|
4550
6720
|
model(model) {
|
|
4551
6721
|
return this.with({ model: model.trim() });
|
|
4552
6722
|
}
|
|
6723
|
+
maxParallelism(n) {
|
|
6724
|
+
return this.with({ maxParallelism: n });
|
|
6725
|
+
}
|
|
6726
|
+
inputs(decls) {
|
|
6727
|
+
return this.with({ inputs: decls });
|
|
6728
|
+
}
|
|
4553
6729
|
node(node) {
|
|
4554
6730
|
return this.with({ nodes: [...this.state.nodes, node] });
|
|
4555
6731
|
}
|
|
@@ -4559,15 +6735,15 @@ var WorkflowIntentBuilder = class _WorkflowIntentBuilder {
|
|
|
4559
6735
|
return this.node(configured.build());
|
|
4560
6736
|
}
|
|
4561
6737
|
joinAll(id) {
|
|
4562
|
-
return this.node({ id, type:
|
|
6738
|
+
return this.node({ id, type: WorkflowNodeTypesIntent.JoinAll });
|
|
4563
6739
|
}
|
|
4564
6740
|
joinAny(id, predicate) {
|
|
4565
|
-
return this.node({ id, type:
|
|
6741
|
+
return this.node({ id, type: WorkflowNodeTypesIntent.JoinAny, predicate });
|
|
4566
6742
|
}
|
|
4567
6743
|
joinCollect(id, options) {
|
|
4568
6744
|
return this.node({
|
|
4569
6745
|
id,
|
|
4570
|
-
type:
|
|
6746
|
+
type: WorkflowNodeTypesIntent.JoinCollect,
|
|
4571
6747
|
limit: options.limit,
|
|
4572
6748
|
timeout_ms: options.timeoutMs,
|
|
4573
6749
|
predicate: options.predicate
|
|
@@ -4576,7 +6752,7 @@ var WorkflowIntentBuilder = class _WorkflowIntentBuilder {
|
|
|
4576
6752
|
transformJSON(id, object, merge) {
|
|
4577
6753
|
return this.node({
|
|
4578
6754
|
id,
|
|
4579
|
-
type:
|
|
6755
|
+
type: WorkflowNodeTypesIntent.TransformJSON,
|
|
4580
6756
|
object,
|
|
4581
6757
|
merge
|
|
4582
6758
|
});
|
|
@@ -4584,7 +6760,7 @@ var WorkflowIntentBuilder = class _WorkflowIntentBuilder {
|
|
|
4584
6760
|
mapFanout(id, options) {
|
|
4585
6761
|
return this.node({
|
|
4586
6762
|
id,
|
|
4587
|
-
type:
|
|
6763
|
+
type: WorkflowNodeTypesIntent.MapFanout,
|
|
4588
6764
|
items_from: options.itemsFrom,
|
|
4589
6765
|
items_from_input: options.itemsFromInput,
|
|
4590
6766
|
items_path: options.itemsPath,
|
|
@@ -4628,6 +6804,8 @@ var WorkflowIntentBuilder = class _WorkflowIntentBuilder {
|
|
|
4628
6804
|
kind: WorkflowKinds.WorkflowIntent,
|
|
4629
6805
|
name: this.state.name,
|
|
4630
6806
|
model: this.state.model,
|
|
6807
|
+
max_parallelism: this.state.maxParallelism,
|
|
6808
|
+
inputs: this.state.inputs,
|
|
4631
6809
|
nodes,
|
|
4632
6810
|
outputs: [...this.state.outputs]
|
|
4633
6811
|
};
|
|
@@ -4635,7 +6813,7 @@ var WorkflowIntentBuilder = class _WorkflowIntentBuilder {
|
|
|
4635
6813
|
};
|
|
4636
6814
|
var LLMNodeBuilder = class {
|
|
4637
6815
|
constructor(id) {
|
|
4638
|
-
this.node = { id, type:
|
|
6816
|
+
this.node = { id, type: WorkflowNodeTypesIntent.LLM };
|
|
4639
6817
|
}
|
|
4640
6818
|
system(text) {
|
|
4641
6819
|
this.node.system = text;
|
|
@@ -4778,154 +6956,6 @@ function createMockFetchQueue(responses) {
|
|
|
4778
6956
|
return { fetch: fetchImpl, calls };
|
|
4779
6957
|
}
|
|
4780
6958
|
|
|
4781
|
-
// src/tools_runner.ts
|
|
4782
|
-
var ToolRunner = class {
|
|
4783
|
-
constructor(options) {
|
|
4784
|
-
this.registry = options.registry;
|
|
4785
|
-
this.runsClient = options.runsClient;
|
|
4786
|
-
this.customerId = options.customerId;
|
|
4787
|
-
this.onBeforeExecute = options.onBeforeExecute;
|
|
4788
|
-
this.onAfterExecute = options.onAfterExecute;
|
|
4789
|
-
this.onSubmitted = options.onSubmitted;
|
|
4790
|
-
this.onError = options.onError;
|
|
4791
|
-
}
|
|
4792
|
-
/**
|
|
4793
|
-
* Handles a node_waiting event by executing tools and submitting results.
|
|
4794
|
-
*
|
|
4795
|
-
* @param runId - The run ID
|
|
4796
|
-
* @param nodeId - The node ID that is waiting
|
|
4797
|
-
* @param waiting - The waiting state with pending tool calls
|
|
4798
|
-
* @returns The submission response with accepted count and new status
|
|
4799
|
-
*
|
|
4800
|
-
* @example
|
|
4801
|
-
* ```typescript
|
|
4802
|
-
* for await (const event of client.runs.events(runId)) {
|
|
4803
|
-
* if (event.type === "node_waiting") {
|
|
4804
|
-
* const result = await runner.handleNodeWaiting(
|
|
4805
|
-
* runId,
|
|
4806
|
-
* event.node_id,
|
|
4807
|
-
* event.waiting
|
|
4808
|
-
* );
|
|
4809
|
-
* console.log(`Submitted ${result.accepted} results, status: ${result.status}`);
|
|
4810
|
-
* }
|
|
4811
|
-
* }
|
|
4812
|
-
* ```
|
|
4813
|
-
*/
|
|
4814
|
-
async handleNodeWaiting(runId, nodeId, waiting) {
|
|
4815
|
-
const results = [];
|
|
4816
|
-
for (const pending of waiting.pending_tool_calls) {
|
|
4817
|
-
try {
|
|
4818
|
-
await this.onBeforeExecute?.(pending);
|
|
4819
|
-
const toolCall = createToolCall(
|
|
4820
|
-
pending.tool_call.id,
|
|
4821
|
-
pending.tool_call.name,
|
|
4822
|
-
pending.tool_call.arguments
|
|
4823
|
-
);
|
|
4824
|
-
const result = await this.registry.execute(toolCall);
|
|
4825
|
-
results.push(result);
|
|
4826
|
-
await this.onAfterExecute?.(result);
|
|
4827
|
-
} catch (err) {
|
|
4828
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
4829
|
-
await this.onError?.(error, pending);
|
|
4830
|
-
results.push({
|
|
4831
|
-
toolCallId: pending.tool_call.id,
|
|
4832
|
-
toolName: pending.tool_call.name,
|
|
4833
|
-
result: null,
|
|
4834
|
-
error: error.message
|
|
4835
|
-
});
|
|
4836
|
-
}
|
|
4837
|
-
}
|
|
4838
|
-
const response = await this.runsClient.submitToolResults(
|
|
4839
|
-
runId,
|
|
4840
|
-
{
|
|
4841
|
-
node_id: nodeId,
|
|
4842
|
-
step: waiting.step,
|
|
4843
|
-
request_id: waiting.request_id,
|
|
4844
|
-
results: results.map((r) => ({
|
|
4845
|
-
tool_call: {
|
|
4846
|
-
id: r.toolCallId,
|
|
4847
|
-
name: r.toolName
|
|
4848
|
-
},
|
|
4849
|
-
output: r.error ? `Error: ${r.error}` : typeof r.result === "string" ? r.result : JSON.stringify(r.result)
|
|
4850
|
-
}))
|
|
4851
|
-
},
|
|
4852
|
-
{ customerId: this.customerId }
|
|
4853
|
-
);
|
|
4854
|
-
await this.onSubmitted?.(runId, response.accepted, response.status);
|
|
4855
|
-
return {
|
|
4856
|
-
accepted: response.accepted,
|
|
4857
|
-
status: response.status,
|
|
4858
|
-
results
|
|
4859
|
-
};
|
|
4860
|
-
}
|
|
4861
|
-
/**
|
|
4862
|
-
* Processes a stream of run events, automatically handling node_waiting events.
|
|
4863
|
-
*
|
|
4864
|
-
* This is the main entry point for running a workflow with client-side tools.
|
|
4865
|
-
* It yields all events through (including node_waiting after handling).
|
|
4866
|
-
*
|
|
4867
|
-
* @param runId - The run ID to process
|
|
4868
|
-
* @param events - AsyncIterable of run events (from RunsClient.events())
|
|
4869
|
-
* @yields All run events, with node_waiting events handled automatically
|
|
4870
|
-
*
|
|
4871
|
-
* @example
|
|
4872
|
-
* ```typescript
|
|
4873
|
-
* const run = await client.runs.create(workflowSpec);
|
|
4874
|
-
* const eventStream = client.runs.events(run.run_id);
|
|
4875
|
-
*
|
|
4876
|
-
* for await (const event of runner.processEvents(run.run_id, eventStream)) {
|
|
4877
|
-
* switch (event.type) {
|
|
4878
|
-
* case "node_started":
|
|
4879
|
-
* console.log(`Node ${event.node_id} started`);
|
|
4880
|
-
* break;
|
|
4881
|
-
* case "node_succeeded":
|
|
4882
|
-
* console.log(`Node ${event.node_id} succeeded`);
|
|
4883
|
-
* break;
|
|
4884
|
-
* case "run_succeeded":
|
|
4885
|
-
* console.log("Run completed!");
|
|
4886
|
-
* break;
|
|
4887
|
-
* }
|
|
4888
|
-
* }
|
|
4889
|
-
* ```
|
|
4890
|
-
*/
|
|
4891
|
-
async *processEvents(runId, events) {
|
|
4892
|
-
for await (const event of events) {
|
|
4893
|
-
if (event.type === "node_waiting") {
|
|
4894
|
-
const waitingEvent = event;
|
|
4895
|
-
try {
|
|
4896
|
-
await this.handleNodeWaiting(
|
|
4897
|
-
runId,
|
|
4898
|
-
waitingEvent.node_id,
|
|
4899
|
-
waitingEvent.waiting
|
|
4900
|
-
);
|
|
4901
|
-
} catch (err) {
|
|
4902
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
4903
|
-
await this.onError?.(error);
|
|
4904
|
-
throw error;
|
|
4905
|
-
}
|
|
4906
|
-
}
|
|
4907
|
-
yield event;
|
|
4908
|
-
}
|
|
4909
|
-
}
|
|
4910
|
-
/**
|
|
4911
|
-
* Checks if a run event is a node_waiting event.
|
|
4912
|
-
* Utility for filtering events when not using processEvents().
|
|
4913
|
-
*/
|
|
4914
|
-
static isNodeWaiting(event) {
|
|
4915
|
-
return event.type === "node_waiting";
|
|
4916
|
-
}
|
|
4917
|
-
/**
|
|
4918
|
-
* Checks if a run status is terminal (succeeded, failed, or canceled).
|
|
4919
|
-
* Utility for determining when to stop polling.
|
|
4920
|
-
*/
|
|
4921
|
-
static isTerminalStatus(status) {
|
|
4922
|
-
return status === "succeeded" || status === "failed" || status === "canceled";
|
|
4923
|
-
}
|
|
4924
|
-
};
|
|
4925
|
-
function createToolRunner(options) {
|
|
4926
|
-
return new ToolRunner(options);
|
|
4927
|
-
}
|
|
4928
|
-
|
|
4929
6959
|
// src/generated/index.ts
|
|
4930
6960
|
var generated_exports = {};
|
|
4931
6961
|
|
|
@@ -4936,7 +6966,7 @@ __export(workflow_exports, {
|
|
|
4936
6966
|
LLMNodeBuilder: () => LLMNodeBuilder,
|
|
4937
6967
|
LLM_TEXT_OUTPUT: () => LLM_TEXT_OUTPUT,
|
|
4938
6968
|
LLM_USER_MESSAGE_TEXT: () => LLM_USER_MESSAGE_TEXT,
|
|
4939
|
-
|
|
6969
|
+
NodeTypesIntent: () => NodeTypesIntent,
|
|
4940
6970
|
WorkflowIntentBuilder: () => WorkflowIntentBuilder,
|
|
4941
6971
|
parseNodeId: () => parseNodeId,
|
|
4942
6972
|
parseOutputName: () => parseOutputName,
|
|
@@ -4945,17 +6975,17 @@ __export(workflow_exports, {
|
|
|
4945
6975
|
workflowIntent: () => workflowIntent
|
|
4946
6976
|
});
|
|
4947
6977
|
var KindIntent = WorkflowKinds.WorkflowIntent;
|
|
4948
|
-
var
|
|
4949
|
-
LLM:
|
|
4950
|
-
JoinAll:
|
|
4951
|
-
JoinAny:
|
|
4952
|
-
JoinCollect:
|
|
4953
|
-
TransformJSON:
|
|
4954
|
-
MapFanout:
|
|
6978
|
+
var NodeTypesIntent = {
|
|
6979
|
+
LLM: WorkflowNodeTypesIntent.LLM,
|
|
6980
|
+
JoinAll: WorkflowNodeTypesIntent.JoinAll,
|
|
6981
|
+
JoinAny: WorkflowNodeTypesIntent.JoinAny,
|
|
6982
|
+
JoinCollect: WorkflowNodeTypesIntent.JoinCollect,
|
|
6983
|
+
TransformJSON: WorkflowNodeTypesIntent.TransformJSON,
|
|
6984
|
+
MapFanout: WorkflowNodeTypesIntent.MapFanout
|
|
4955
6985
|
};
|
|
4956
6986
|
|
|
4957
6987
|
// src/index.ts
|
|
4958
|
-
var
|
|
6988
|
+
var _ModelRelay = class _ModelRelay {
|
|
4959
6989
|
static fromSecretKey(secretKey, options = {}) {
|
|
4960
6990
|
return new _ModelRelay({ ...options, key: parseSecretKey(secretKey) });
|
|
4961
6991
|
}
|
|
@@ -5004,22 +7034,188 @@ var ModelRelay = class _ModelRelay {
|
|
|
5004
7034
|
});
|
|
5005
7035
|
this.images = new ImagesClient(this.http, auth);
|
|
5006
7036
|
this.sessions = new SessionsClient(this, this.http, auth);
|
|
7037
|
+
this.stateHandles = new StateHandlesClient(this.http, auth);
|
|
7038
|
+
this.messages = new MessagesClient(this.http, auth);
|
|
5007
7039
|
this.tiers = new TiersClient(this.http, { apiKey, accessToken });
|
|
7040
|
+
this.plugins = new PluginsClient({
|
|
7041
|
+
responses: this.responses,
|
|
7042
|
+
http: this.http,
|
|
7043
|
+
auth,
|
|
7044
|
+
runs: this.runs
|
|
7045
|
+
});
|
|
5008
7046
|
}
|
|
5009
7047
|
forCustomer(customerId) {
|
|
5010
7048
|
return new CustomerScopedModelRelay(this.responses, customerId, this.baseUrl);
|
|
5011
7049
|
}
|
|
7050
|
+
/**
|
|
7051
|
+
* Simple chat completion with system and user prompt.
|
|
7052
|
+
*
|
|
7053
|
+
* Returns the full Response object for access to usage, model, etc.
|
|
7054
|
+
*
|
|
7055
|
+
* @example
|
|
7056
|
+
* ```typescript
|
|
7057
|
+
* const response = await mr.chat("claude-sonnet-4-5", "Hello!");
|
|
7058
|
+
* console.log(response.output);
|
|
7059
|
+
* console.log(response.usage);
|
|
7060
|
+
* ```
|
|
7061
|
+
*
|
|
7062
|
+
* @example With system prompt
|
|
7063
|
+
* ```typescript
|
|
7064
|
+
* const response = await mr.chat("claude-sonnet-4-5", "Explain quantum computing", {
|
|
7065
|
+
* system: "You are a physics professor",
|
|
7066
|
+
* });
|
|
7067
|
+
* ```
|
|
7068
|
+
*/
|
|
7069
|
+
async chat(model, prompt, options = {}) {
|
|
7070
|
+
const { system, customerId, ...reqOptions } = options;
|
|
7071
|
+
let builder = this.responses.new().model(asModelId(model));
|
|
7072
|
+
if (system) {
|
|
7073
|
+
builder = builder.system(system);
|
|
7074
|
+
}
|
|
7075
|
+
builder = builder.user(prompt);
|
|
7076
|
+
if (customerId) {
|
|
7077
|
+
builder = builder.customerId(customerId);
|
|
7078
|
+
}
|
|
7079
|
+
return this.responses.create(builder.build(), reqOptions);
|
|
7080
|
+
}
|
|
7081
|
+
/**
|
|
7082
|
+
* Simple prompt that returns just the text response.
|
|
7083
|
+
*
|
|
7084
|
+
* The most ergonomic way to get a quick answer.
|
|
7085
|
+
*
|
|
7086
|
+
* @example
|
|
7087
|
+
* ```typescript
|
|
7088
|
+
* const answer = await mr.ask("claude-sonnet-4-5", "What is 2 + 2?");
|
|
7089
|
+
* console.log(answer); // "4"
|
|
7090
|
+
* ```
|
|
7091
|
+
*
|
|
7092
|
+
* @example With system prompt
|
|
7093
|
+
* ```typescript
|
|
7094
|
+
* const haiku = await mr.ask("claude-sonnet-4-5", "Write about the ocean", {
|
|
7095
|
+
* system: "You are a poet who only writes haikus",
|
|
7096
|
+
* });
|
|
7097
|
+
* ```
|
|
7098
|
+
*/
|
|
7099
|
+
async ask(model, prompt, options = {}) {
|
|
7100
|
+
const response = await this.chat(model, prompt, options);
|
|
7101
|
+
return extractAssistantText(response.output);
|
|
7102
|
+
}
|
|
7103
|
+
/**
|
|
7104
|
+
* Run an agentic tool loop to completion.
|
|
7105
|
+
*
|
|
7106
|
+
* Runs API calls in a loop until the model stops calling tools
|
|
7107
|
+
* or maxTurns is reached.
|
|
7108
|
+
*
|
|
7109
|
+
* @example
|
|
7110
|
+
* ```typescript
|
|
7111
|
+
* import { z } from "zod";
|
|
7112
|
+
*
|
|
7113
|
+
* const tools = mr.tools()
|
|
7114
|
+
* .add("read_file", "Read a file", z.object({ path: z.string() }), async (args) => {
|
|
7115
|
+
* return fs.readFile(args.path, "utf-8");
|
|
7116
|
+
* })
|
|
7117
|
+
* .add("write_file", "Write a file", z.object({ path: z.string(), content: z.string() }), async (args) => {
|
|
7118
|
+
* await fs.writeFile(args.path, args.content);
|
|
7119
|
+
* return "File written successfully";
|
|
7120
|
+
* });
|
|
7121
|
+
*
|
|
7122
|
+
* const result = await mr.agent("claude-sonnet-4-5", {
|
|
7123
|
+
* tools,
|
|
7124
|
+
* prompt: "Read config.json and add a version field",
|
|
7125
|
+
* });
|
|
7126
|
+
*
|
|
7127
|
+
* console.log(result.output); // Final text response
|
|
7128
|
+
* console.log(result.usage); // Total tokens used
|
|
7129
|
+
* ```
|
|
7130
|
+
*
|
|
7131
|
+
* @example With system prompt and maxTurns
|
|
7132
|
+
* ```typescript
|
|
7133
|
+
* const result = await mr.agent("claude-sonnet-4-5", {
|
|
7134
|
+
* tools,
|
|
7135
|
+
* prompt: "Refactor the auth module",
|
|
7136
|
+
* system: "You are a senior TypeScript developer",
|
|
7137
|
+
* maxTurns: 50, // or ModelRelay.NO_TURN_LIMIT for unlimited
|
|
7138
|
+
* });
|
|
7139
|
+
* ```
|
|
7140
|
+
*/
|
|
7141
|
+
async agent(model, options) {
|
|
7142
|
+
const { definitions, registry } = options.tools.build();
|
|
7143
|
+
const maxTurns = options.maxTurns ?? _ModelRelay.DEFAULT_MAX_TURNS;
|
|
7144
|
+
const modelId = asModelId(model);
|
|
7145
|
+
const input = [];
|
|
7146
|
+
if (options.system) {
|
|
7147
|
+
input.push(createSystemMessage(options.system));
|
|
7148
|
+
}
|
|
7149
|
+
input.push(createUserMessage(options.prompt));
|
|
7150
|
+
const outcome = await runToolLoop({
|
|
7151
|
+
client: this.responses,
|
|
7152
|
+
input,
|
|
7153
|
+
tools: definitions,
|
|
7154
|
+
registry,
|
|
7155
|
+
maxTurns,
|
|
7156
|
+
buildRequest: (builder) => builder.model(modelId)
|
|
7157
|
+
});
|
|
7158
|
+
if (outcome.status !== "complete") {
|
|
7159
|
+
throw new ConfigError("agent tool loop requires a tool registry");
|
|
7160
|
+
}
|
|
7161
|
+
return {
|
|
7162
|
+
output: outcome.output,
|
|
7163
|
+
usage: outcome.usage,
|
|
7164
|
+
response: outcome.response
|
|
7165
|
+
};
|
|
7166
|
+
}
|
|
7167
|
+
/**
|
|
7168
|
+
* Creates a fluent tool builder for defining tools with Zod schemas.
|
|
7169
|
+
*
|
|
7170
|
+
* @example
|
|
7171
|
+
* ```typescript
|
|
7172
|
+
* import { z } from "zod";
|
|
7173
|
+
*
|
|
7174
|
+
* const tools = mr.tools()
|
|
7175
|
+
* .add("get_weather", "Get current weather", z.object({ location: z.string() }), async (args) => {
|
|
7176
|
+
* return { temp: 72, unit: "fahrenheit" };
|
|
7177
|
+
* })
|
|
7178
|
+
* .add("read_file", "Read a file", z.object({ path: z.string() }), async (args) => {
|
|
7179
|
+
* return fs.readFile(args.path, "utf-8");
|
|
7180
|
+
* });
|
|
7181
|
+
*
|
|
7182
|
+
* // Use with agent (pass ToolBuilder directly)
|
|
7183
|
+
* const result = await mr.agent("claude-sonnet-4-5", {
|
|
7184
|
+
* tools,
|
|
7185
|
+
* prompt: "What's the weather in Paris?",
|
|
7186
|
+
* });
|
|
7187
|
+
*
|
|
7188
|
+
* // Or get tool definitions for manual use
|
|
7189
|
+
* const toolDefs = tools.definitions();
|
|
7190
|
+
* ```
|
|
7191
|
+
*/
|
|
7192
|
+
tools() {
|
|
7193
|
+
return new ToolBuilder();
|
|
7194
|
+
}
|
|
5012
7195
|
};
|
|
7196
|
+
// =========================================================================
|
|
7197
|
+
// Convenience Methods (Simple Case Simple)
|
|
7198
|
+
// =========================================================================
|
|
7199
|
+
/** Default maximum turns for agent loops. */
|
|
7200
|
+
_ModelRelay.DEFAULT_MAX_TURNS = 100;
|
|
7201
|
+
/**
|
|
7202
|
+
* Use this for maxTurns to disable the turn limit.
|
|
7203
|
+
* Use with caution as this can lead to infinite loops and runaway API costs.
|
|
7204
|
+
*/
|
|
7205
|
+
_ModelRelay.NO_TURN_LIMIT = Number.MAX_SAFE_INTEGER;
|
|
7206
|
+
var ModelRelay = _ModelRelay;
|
|
5013
7207
|
function resolveBaseUrl(override) {
|
|
5014
7208
|
const base = override || DEFAULT_BASE_URL;
|
|
5015
7209
|
return base.replace(/\/+$/, "");
|
|
5016
7210
|
}
|
|
5017
7211
|
export {
|
|
5018
7212
|
APIError,
|
|
7213
|
+
AgentMaxTurnsError,
|
|
5019
7214
|
AuthClient,
|
|
5020
7215
|
BillingProviders,
|
|
5021
7216
|
ConfigError,
|
|
5022
7217
|
ContentPartTypes,
|
|
7218
|
+
ContextManager,
|
|
5023
7219
|
CustomerResponsesClient,
|
|
5024
7220
|
CustomerScopedModelRelay,
|
|
5025
7221
|
CustomerTokenProvider,
|
|
@@ -5028,6 +7224,7 @@ export {
|
|
|
5028
7224
|
DEFAULT_CONNECT_TIMEOUT_MS,
|
|
5029
7225
|
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
5030
7226
|
ErrorCodes,
|
|
7227
|
+
FileConversationStore,
|
|
5031
7228
|
ImagesClient,
|
|
5032
7229
|
InputItemTypes,
|
|
5033
7230
|
JoinOutput,
|
|
@@ -5048,19 +7245,30 @@ export {
|
|
|
5048
7245
|
LLM_TEXT_OUTPUT,
|
|
5049
7246
|
LLM_USER_MESSAGE_TEXT,
|
|
5050
7247
|
LocalSession,
|
|
5051
|
-
|
|
7248
|
+
MAX_STATE_HANDLE_TTL_SECONDS,
|
|
7249
|
+
MemoryConversationStore,
|
|
5052
7250
|
MessageRoles,
|
|
7251
|
+
MessagesClient,
|
|
5053
7252
|
ModelRelay,
|
|
5054
7253
|
ModelRelayError,
|
|
7254
|
+
OrchestrationModes,
|
|
5055
7255
|
OutputFormatTypes,
|
|
5056
7256
|
OutputItemTypes,
|
|
5057
7257
|
PathEscapeError,
|
|
7258
|
+
PluginConverter,
|
|
7259
|
+
PluginLoader,
|
|
7260
|
+
PluginOrchestrationError,
|
|
7261
|
+
PluginOrchestrationErrorCodes,
|
|
7262
|
+
PluginRunner,
|
|
7263
|
+
PluginToolNames,
|
|
7264
|
+
PluginsClient,
|
|
5058
7265
|
ResponsesClient,
|
|
5059
7266
|
ResponsesStream,
|
|
5060
7267
|
RunsClient,
|
|
5061
7268
|
RunsEventStream,
|
|
5062
7269
|
SDK_VERSION,
|
|
5063
7270
|
SessionsClient,
|
|
7271
|
+
SqliteConversationStore,
|
|
5064
7272
|
StopReasons,
|
|
5065
7273
|
StreamProtocolError,
|
|
5066
7274
|
StreamTimeoutError,
|
|
@@ -5071,18 +7279,19 @@ export {
|
|
|
5071
7279
|
TiersClient,
|
|
5072
7280
|
ToolArgsError,
|
|
5073
7281
|
ToolArgumentError,
|
|
7282
|
+
ToolBuilder,
|
|
5074
7283
|
ToolCallAccumulator,
|
|
5075
7284
|
ToolChoiceTypes,
|
|
5076
7285
|
ToolRegistry,
|
|
5077
7286
|
ToolRunner,
|
|
5078
7287
|
ToolTypes,
|
|
5079
7288
|
TransportError,
|
|
7289
|
+
USER_ASK_TOOL_NAME,
|
|
5080
7290
|
WORKFLOWS_COMPILE_PATH,
|
|
5081
|
-
WebToolIntents,
|
|
5082
7291
|
WorkflowIntentBuilder,
|
|
5083
7292
|
WorkflowKinds,
|
|
5084
|
-
|
|
5085
|
-
|
|
7293
|
+
WorkflowNodeTypesIntent as WorkflowNodeTypes,
|
|
7294
|
+
WorkflowNodeTypesIntent,
|
|
5086
7295
|
WorkflowValidationError,
|
|
5087
7296
|
WorkflowsClient,
|
|
5088
7297
|
asModelId,
|
|
@@ -5096,19 +7305,22 @@ export {
|
|
|
5096
7305
|
createAccessTokenAuth,
|
|
5097
7306
|
createApiKeyAuth,
|
|
5098
7307
|
createAssistantMessage,
|
|
7308
|
+
createFileConversationStore,
|
|
5099
7309
|
createFunctionCall,
|
|
5100
7310
|
createFunctionTool,
|
|
5101
|
-
createFunctionToolFromSchema,
|
|
5102
7311
|
createLocalSession,
|
|
5103
|
-
|
|
7312
|
+
createMemoryConversationStore,
|
|
5104
7313
|
createMockFetchQueue,
|
|
7314
|
+
createModelContextResolver,
|
|
5105
7315
|
createRetryMessages,
|
|
7316
|
+
createSqliteConversationStore,
|
|
5106
7317
|
createSystemMessage,
|
|
5107
7318
|
createToolCall,
|
|
5108
7319
|
createToolRunner,
|
|
7320
|
+
createTypedTool,
|
|
5109
7321
|
createUsage,
|
|
7322
|
+
createUserAskTool,
|
|
5110
7323
|
createUserMessage,
|
|
5111
|
-
createWebTool,
|
|
5112
7324
|
defaultRetryHandler,
|
|
5113
7325
|
defaultTierModelId,
|
|
5114
7326
|
executeWithRetry,
|
|
@@ -5116,10 +7328,18 @@ export {
|
|
|
5116
7328
|
formatToolErrorForModel,
|
|
5117
7329
|
generateSessionId,
|
|
5118
7330
|
generated_exports as generated,
|
|
7331
|
+
getAllToolCalls,
|
|
7332
|
+
getAssistantText,
|
|
5119
7333
|
getRetryableErrors,
|
|
7334
|
+
getToolArgs,
|
|
7335
|
+
getToolArgsRaw,
|
|
7336
|
+
getToolName,
|
|
7337
|
+
getTypedToolCall,
|
|
7338
|
+
getTypedToolCalls,
|
|
5120
7339
|
hasRetryableErrors,
|
|
5121
7340
|
hasToolCalls,
|
|
5122
7341
|
isSecretKey,
|
|
7342
|
+
isUserAskToolCall,
|
|
5123
7343
|
llm,
|
|
5124
7344
|
mergeMetrics,
|
|
5125
7345
|
mergeTrace,
|
|
@@ -5135,15 +7355,20 @@ export {
|
|
|
5135
7355
|
parsePlanHash,
|
|
5136
7356
|
parseRunId,
|
|
5137
7357
|
parseSecretKey,
|
|
5138
|
-
|
|
5139
|
-
|
|
7358
|
+
parseTypedToolCall,
|
|
7359
|
+
parseUserAskArgs,
|
|
7360
|
+
prepareInputWithContext,
|
|
5140
7361
|
respondToToolCall,
|
|
7362
|
+
runToolLoop,
|
|
7363
|
+
serializeUserAskResult,
|
|
5141
7364
|
stopReasonToString,
|
|
5142
7365
|
toolChoiceAuto,
|
|
5143
7366
|
toolChoiceNone,
|
|
5144
7367
|
toolChoiceRequired,
|
|
5145
7368
|
toolResultMessage,
|
|
5146
|
-
|
|
7369
|
+
truncateInputByTokens,
|
|
7370
|
+
userAskResultChoice,
|
|
7371
|
+
userAskResultFreeform,
|
|
5147
7372
|
validateWithZod,
|
|
5148
7373
|
workflow_exports as workflow,
|
|
5149
7374
|
workflowIntent,
|