@modelrelay/sdk 1.3.2 → 1.10.3
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 +57 -8
- package/dist/index.cjs +3476 -1008
- package/dist/index.d.cts +2199 -321
- package/dist/index.d.ts +2199 -321
- package/dist/index.js +3441 -1008
- package/package.json +15 -3
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -22,6 +32,10 @@ var index_exports = {};
|
|
|
22
32
|
__export(index_exports, {
|
|
23
33
|
APIError: () => APIError,
|
|
24
34
|
AuthClient: () => AuthClient,
|
|
35
|
+
BillingProviders: () => BillingProviders,
|
|
36
|
+
BrowserDefaults: () => BrowserDefaults,
|
|
37
|
+
BrowserToolNames: () => BrowserToolNames,
|
|
38
|
+
BrowserToolPack: () => BrowserToolPack,
|
|
25
39
|
ConfigError: () => ConfigError,
|
|
26
40
|
ContentPartTypes: () => ContentPartTypes,
|
|
27
41
|
CustomerResponsesClient: () => CustomerResponsesClient,
|
|
@@ -31,10 +45,17 @@ __export(index_exports, {
|
|
|
31
45
|
DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
|
|
32
46
|
DEFAULT_CLIENT_HEADER: () => DEFAULT_CLIENT_HEADER,
|
|
33
47
|
DEFAULT_CONNECT_TIMEOUT_MS: () => DEFAULT_CONNECT_TIMEOUT_MS,
|
|
48
|
+
DEFAULT_IGNORE_DIRS: () => DEFAULT_IGNORE_DIRS,
|
|
34
49
|
DEFAULT_REQUEST_TIMEOUT_MS: () => DEFAULT_REQUEST_TIMEOUT_MS,
|
|
35
50
|
ErrorCodes: () => ErrorCodes,
|
|
51
|
+
FSDefaults: () => FSDefaults,
|
|
52
|
+
FSToolNames: () => ToolNames,
|
|
36
53
|
FrontendTokenProvider: () => FrontendTokenProvider,
|
|
54
|
+
ImagesClient: () => ImagesClient,
|
|
37
55
|
InputItemTypes: () => InputItemTypes,
|
|
56
|
+
LocalFSToolPack: () => LocalFSToolPack,
|
|
57
|
+
LocalSession: () => LocalSession,
|
|
58
|
+
MemorySessionStore: () => MemorySessionStore,
|
|
38
59
|
MessageRoles: () => MessageRoles,
|
|
39
60
|
ModelRelay: () => ModelRelay,
|
|
40
61
|
ModelRelayError: () => ModelRelayError,
|
|
@@ -42,22 +63,27 @@ __export(index_exports, {
|
|
|
42
63
|
OIDCExchangeTokenProvider: () => OIDCExchangeTokenProvider,
|
|
43
64
|
OutputFormatTypes: () => OutputFormatTypes,
|
|
44
65
|
OutputItemTypes: () => OutputItemTypes,
|
|
66
|
+
PathEscapeError: () => PathEscapeError,
|
|
45
67
|
ResponsesClient: () => ResponsesClient,
|
|
46
68
|
ResponsesStream: () => ResponsesStream,
|
|
47
69
|
RunsClient: () => RunsClient,
|
|
48
70
|
RunsEventStream: () => RunsEventStream,
|
|
49
71
|
SDK_VERSION: () => SDK_VERSION,
|
|
72
|
+
SessionsClient: () => SessionsClient,
|
|
50
73
|
StopReasons: () => StopReasons,
|
|
51
74
|
StreamProtocolError: () => StreamProtocolError,
|
|
52
75
|
StreamTimeoutError: () => StreamTimeoutError,
|
|
53
76
|
StructuredDecodeError: () => StructuredDecodeError,
|
|
54
77
|
StructuredExhaustedError: () => StructuredExhaustedError,
|
|
55
78
|
StructuredJSONStream: () => StructuredJSONStream,
|
|
79
|
+
SubscriptionStatuses: () => SubscriptionStatuses,
|
|
56
80
|
TiersClient: () => TiersClient,
|
|
57
81
|
ToolArgsError: () => ToolArgsError,
|
|
82
|
+
ToolArgumentError: () => ToolArgumentError,
|
|
58
83
|
ToolCallAccumulator: () => ToolCallAccumulator,
|
|
59
84
|
ToolChoiceTypes: () => ToolChoiceTypes,
|
|
60
85
|
ToolRegistry: () => ToolRegistry,
|
|
86
|
+
ToolRunner: () => ToolRunner,
|
|
61
87
|
ToolTypes: () => ToolTypes,
|
|
62
88
|
TransportError: () => TransportError,
|
|
63
89
|
WORKFLOWS_COMPILE_PATH: () => WORKFLOWS_COMPILE_PATH,
|
|
@@ -69,17 +95,25 @@ __export(index_exports, {
|
|
|
69
95
|
WorkflowsClient: () => WorkflowsClient,
|
|
70
96
|
asModelId: () => asModelId,
|
|
71
97
|
asProviderId: () => asProviderId,
|
|
98
|
+
asSessionId: () => asSessionId,
|
|
72
99
|
asTierCode: () => asTierCode,
|
|
73
100
|
assistantMessageWithToolCalls: () => assistantMessageWithToolCalls,
|
|
74
101
|
createAccessTokenAuth: () => createAccessTokenAuth,
|
|
75
102
|
createApiKeyAuth: () => createApiKeyAuth,
|
|
76
103
|
createAssistantMessage: () => createAssistantMessage,
|
|
104
|
+
createBrowserToolPack: () => createBrowserToolPack,
|
|
105
|
+
createBrowserTools: () => createBrowserTools,
|
|
77
106
|
createFunctionCall: () => createFunctionCall,
|
|
78
107
|
createFunctionTool: () => createFunctionTool,
|
|
79
108
|
createFunctionToolFromSchema: () => createFunctionToolFromSchema,
|
|
109
|
+
createLocalFSToolPack: () => createLocalFSToolPack,
|
|
110
|
+
createLocalFSTools: () => createLocalFSTools,
|
|
111
|
+
createLocalSession: () => createLocalSession,
|
|
112
|
+
createMemorySessionStore: () => createMemorySessionStore,
|
|
80
113
|
createRetryMessages: () => createRetryMessages,
|
|
81
114
|
createSystemMessage: () => createSystemMessage,
|
|
82
115
|
createToolCall: () => createToolCall,
|
|
116
|
+
createToolRunner: () => createToolRunner,
|
|
83
117
|
createUsage: () => createUsage,
|
|
84
118
|
createUserMessage: () => createUserMessage,
|
|
85
119
|
createWebTool: () => createWebTool,
|
|
@@ -87,6 +121,7 @@ __export(index_exports, {
|
|
|
87
121
|
executeWithRetry: () => executeWithRetry,
|
|
88
122
|
firstToolCall: () => firstToolCall,
|
|
89
123
|
formatToolErrorForModel: () => formatToolErrorForModel,
|
|
124
|
+
generateSessionId: () => generateSessionId,
|
|
90
125
|
generated: () => generated_exports,
|
|
91
126
|
getRetryableErrors: () => getRetryableErrors,
|
|
92
127
|
hasRetryableErrors: () => hasRetryableErrors,
|
|
@@ -289,6 +324,28 @@ var WorkflowValidationError = class extends ModelRelayError {
|
|
|
289
324
|
this.issues = opts.issues;
|
|
290
325
|
}
|
|
291
326
|
};
|
|
327
|
+
var ToolArgumentError = class extends ModelRelayError {
|
|
328
|
+
constructor(opts) {
|
|
329
|
+
super(opts.message, {
|
|
330
|
+
category: "config",
|
|
331
|
+
status: 400,
|
|
332
|
+
cause: opts.cause
|
|
333
|
+
});
|
|
334
|
+
this.toolCallId = opts.toolCallId;
|
|
335
|
+
this.toolName = opts.toolName;
|
|
336
|
+
this.rawArguments = opts.rawArguments;
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
var PathEscapeError = class extends ModelRelayError {
|
|
340
|
+
constructor(opts) {
|
|
341
|
+
super(`path escapes sandbox: ${opts.requestedPath}`, {
|
|
342
|
+
category: "config",
|
|
343
|
+
status: 403
|
|
344
|
+
});
|
|
345
|
+
this.requestedPath = opts.requestedPath;
|
|
346
|
+
this.resolvedPath = opts.resolvedPath;
|
|
347
|
+
}
|
|
348
|
+
};
|
|
292
349
|
function isEmailRequired(err) {
|
|
293
350
|
return err instanceof APIError && err.isEmailRequired();
|
|
294
351
|
}
|
|
@@ -335,10 +392,10 @@ async function parseErrorResponse(response, retries) {
|
|
|
335
392
|
if (!raw || typeof raw !== "object") continue;
|
|
336
393
|
const obj = raw;
|
|
337
394
|
const code = typeof obj.code === "string" ? obj.code : "";
|
|
338
|
-
const
|
|
395
|
+
const path2 = typeof obj.path === "string" ? obj.path : "";
|
|
339
396
|
const message = typeof obj.message === "string" ? obj.message : "";
|
|
340
|
-
if (!code || !
|
|
341
|
-
normalized.push({ code, path, message });
|
|
397
|
+
if (!code || !path2 || !message) continue;
|
|
398
|
+
normalized.push({ code, path: path2, message });
|
|
342
399
|
}
|
|
343
400
|
if (normalized.length > 0) {
|
|
344
401
|
return new WorkflowValidationError({
|
|
@@ -579,24 +636,18 @@ var AuthClient = class {
|
|
|
579
636
|
* Mint a customer-scoped bearer token (requires a secret key).
|
|
580
637
|
*/
|
|
581
638
|
async customerToken(request) {
|
|
582
|
-
const projectId = request.projectId?.trim();
|
|
583
|
-
if (!projectId) {
|
|
584
|
-
throw new ConfigError("projectId is required");
|
|
585
|
-
}
|
|
586
639
|
const customerId = request.customerId?.trim();
|
|
587
640
|
const customerExternalId = request.customerExternalId?.trim();
|
|
588
641
|
if (!!customerId && !!customerExternalId || !customerId && !customerExternalId) {
|
|
589
642
|
throw new ConfigError("Provide exactly one of customerId or customerExternalId");
|
|
590
643
|
}
|
|
591
|
-
if (request.ttlSeconds !== void 0 && request.ttlSeconds
|
|
592
|
-
throw new ConfigError("ttlSeconds must be
|
|
644
|
+
if (request.ttlSeconds !== void 0 && request.ttlSeconds < 0) {
|
|
645
|
+
throw new ConfigError("ttlSeconds must be non-negative when provided");
|
|
593
646
|
}
|
|
594
647
|
if (!this.apiKey || this.apiKeyIsPublishable) {
|
|
595
648
|
throw new ConfigError("Secret API key is required to mint customer tokens");
|
|
596
649
|
}
|
|
597
|
-
const payload = {
|
|
598
|
-
project_id: projectId
|
|
599
|
-
};
|
|
650
|
+
const payload = {};
|
|
600
651
|
if (customerId) {
|
|
601
652
|
payload.customer_id = customerId;
|
|
602
653
|
}
|
|
@@ -694,8 +745,8 @@ var AuthClient = class {
|
|
|
694
745
|
params.set("provider", request.provider);
|
|
695
746
|
}
|
|
696
747
|
const queryString = params.toString();
|
|
697
|
-
const
|
|
698
|
-
const apiResp = await this.http.json(
|
|
748
|
+
const path2 = queryString ? `/auth/device/start?${queryString}` : "/auth/device/start";
|
|
749
|
+
const apiResp = await this.http.json(path2, {
|
|
699
750
|
method: "POST",
|
|
700
751
|
apiKey: this.apiKey
|
|
701
752
|
});
|
|
@@ -759,7 +810,7 @@ var AuthClient = class {
|
|
|
759
810
|
token: apiResp.token,
|
|
760
811
|
expiresAt: new Date(apiResp.expires_at),
|
|
761
812
|
expiresIn: apiResp.expires_in,
|
|
762
|
-
tokenType:
|
|
813
|
+
tokenType: "Bearer",
|
|
763
814
|
projectId: apiResp.project_id,
|
|
764
815
|
customerId: apiResp.customer_id,
|
|
765
816
|
customerExternalId: apiResp.customer_external_id,
|
|
@@ -818,7 +869,7 @@ function isTokenReusable(token) {
|
|
|
818
869
|
// package.json
|
|
819
870
|
var package_default = {
|
|
820
871
|
name: "@modelrelay/sdk",
|
|
821
|
-
version: "1.3
|
|
872
|
+
version: "1.10.3",
|
|
822
873
|
description: "TypeScript SDK for the ModelRelay API",
|
|
823
874
|
type: "module",
|
|
824
875
|
main: "dist/index.cjs",
|
|
@@ -831,12 +882,14 @@ var package_default = {
|
|
|
831
882
|
require: "./dist/index.cjs"
|
|
832
883
|
}
|
|
833
884
|
},
|
|
834
|
-
publishConfig: {
|
|
885
|
+
publishConfig: {
|
|
886
|
+
access: "public"
|
|
887
|
+
},
|
|
835
888
|
files: [
|
|
836
889
|
"dist"
|
|
837
890
|
],
|
|
838
891
|
scripts: {
|
|
839
|
-
build: "tsup src/index.ts --format esm,cjs --dts",
|
|
892
|
+
build: "tsup src/index.ts --format esm,cjs --dts --external playwright",
|
|
840
893
|
dev: "tsup src/index.ts --format esm,cjs --dts --watch",
|
|
841
894
|
lint: "tsc --noEmit --project tsconfig.lint.json",
|
|
842
895
|
test: "vitest run",
|
|
@@ -853,8 +906,18 @@ var package_default = {
|
|
|
853
906
|
dependencies: {
|
|
854
907
|
"fast-json-patch": "^3.1.1"
|
|
855
908
|
},
|
|
909
|
+
peerDependencies: {
|
|
910
|
+
playwright: ">=1.40.0"
|
|
911
|
+
},
|
|
912
|
+
peerDependenciesMeta: {
|
|
913
|
+
playwright: {
|
|
914
|
+
optional: true
|
|
915
|
+
}
|
|
916
|
+
},
|
|
856
917
|
devDependencies: {
|
|
918
|
+
"@types/node": "^25.0.3",
|
|
857
919
|
"openapi-typescript": "^7.4.4",
|
|
920
|
+
playwright: "^1.49.0",
|
|
858
921
|
tsup: "^8.2.4",
|
|
859
922
|
typescript: "^5.6.3",
|
|
860
923
|
vitest: "^2.1.4",
|
|
@@ -891,6 +954,22 @@ function asModelId(value) {
|
|
|
891
954
|
function asTierCode(value) {
|
|
892
955
|
return value;
|
|
893
956
|
}
|
|
957
|
+
var SubscriptionStatuses = {
|
|
958
|
+
Active: "active",
|
|
959
|
+
Trialing: "trialing",
|
|
960
|
+
PastDue: "past_due",
|
|
961
|
+
Canceled: "canceled",
|
|
962
|
+
Unpaid: "unpaid",
|
|
963
|
+
Incomplete: "incomplete",
|
|
964
|
+
IncompleteExpired: "incomplete_expired",
|
|
965
|
+
Paused: "paused"
|
|
966
|
+
};
|
|
967
|
+
var BillingProviders = {
|
|
968
|
+
Stripe: "stripe",
|
|
969
|
+
Crypto: "crypto",
|
|
970
|
+
AppStore: "app_store",
|
|
971
|
+
External: "external"
|
|
972
|
+
};
|
|
894
973
|
function createUsage(inputTokens, outputTokens, totalTokens) {
|
|
895
974
|
return {
|
|
896
975
|
inputTokens,
|
|
@@ -1408,8 +1487,8 @@ function parseToolArgs(call, schema) {
|
|
|
1408
1487
|
const zodErr = err;
|
|
1409
1488
|
if (zodErr.errors && Array.isArray(zodErr.errors)) {
|
|
1410
1489
|
const issues = zodErr.errors.map((e) => {
|
|
1411
|
-
const
|
|
1412
|
-
return `${
|
|
1490
|
+
const path2 = e.path.length > 0 ? `${e.path.join(".")}: ` : "";
|
|
1491
|
+
return `${path2}${e.message}`;
|
|
1413
1492
|
}).join("; ");
|
|
1414
1493
|
message = issues;
|
|
1415
1494
|
} else {
|
|
@@ -1533,7 +1612,7 @@ var ToolRegistry = class {
|
|
|
1533
1612
|
result
|
|
1534
1613
|
};
|
|
1535
1614
|
} catch (err) {
|
|
1536
|
-
const isRetryable = err instanceof ToolArgsError;
|
|
1615
|
+
const isRetryable = err instanceof ToolArgsError || err instanceof ToolArgumentError;
|
|
1537
1616
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1538
1617
|
return {
|
|
1539
1618
|
toolCallId: call.id,
|
|
@@ -2053,6 +2132,7 @@ function mapNDJSONResponseEvent(line, requestId) {
|
|
|
2053
2132
|
}
|
|
2054
2133
|
const toolCallDelta = extractToolCallDelta(parsed, type);
|
|
2055
2134
|
const toolCalls = extractToolCalls(parsed, type);
|
|
2135
|
+
const toolResult = extractToolResult(parsed, type);
|
|
2056
2136
|
return {
|
|
2057
2137
|
type,
|
|
2058
2138
|
event: recordType2,
|
|
@@ -2060,6 +2140,7 @@ function mapNDJSONResponseEvent(line, requestId) {
|
|
|
2060
2140
|
textDelta,
|
|
2061
2141
|
toolCallDelta,
|
|
2062
2142
|
toolCalls,
|
|
2143
|
+
toolResult,
|
|
2063
2144
|
responseId,
|
|
2064
2145
|
model,
|
|
2065
2146
|
stopReason,
|
|
@@ -2123,6 +2204,15 @@ function extractToolCalls(payload, type) {
|
|
|
2123
2204
|
}
|
|
2124
2205
|
return void 0;
|
|
2125
2206
|
}
|
|
2207
|
+
function extractToolResult(payload, type) {
|
|
2208
|
+
if (type !== "tool_use_stop") {
|
|
2209
|
+
return void 0;
|
|
2210
|
+
}
|
|
2211
|
+
if ("tool_result" in payload) {
|
|
2212
|
+
return payload.tool_result;
|
|
2213
|
+
}
|
|
2214
|
+
return void 0;
|
|
2215
|
+
}
|
|
2126
2216
|
function normalizeToolCalls(toolCalls) {
|
|
2127
2217
|
const validToolTypes = /* @__PURE__ */ new Set([
|
|
2128
2218
|
"function",
|
|
@@ -3533,8 +3623,8 @@ function getErrorMap() {
|
|
|
3533
3623
|
|
|
3534
3624
|
// node_modules/zod/v3/helpers/parseUtil.js
|
|
3535
3625
|
var makeIssue = (params) => {
|
|
3536
|
-
const { data, path, errorMaps, issueData } = params;
|
|
3537
|
-
const fullPath = [...
|
|
3626
|
+
const { data, path: path2, errorMaps, issueData } = params;
|
|
3627
|
+
const fullPath = [...path2, ...issueData.path || []];
|
|
3538
3628
|
const fullIssue = {
|
|
3539
3629
|
...issueData,
|
|
3540
3630
|
path: fullPath
|
|
@@ -3650,11 +3740,11 @@ var errorUtil;
|
|
|
3650
3740
|
|
|
3651
3741
|
// node_modules/zod/v3/types.js
|
|
3652
3742
|
var ParseInputLazyPath = class {
|
|
3653
|
-
constructor(parent, value,
|
|
3743
|
+
constructor(parent, value, path2, key) {
|
|
3654
3744
|
this._cachedPath = [];
|
|
3655
3745
|
this.parent = parent;
|
|
3656
3746
|
this.data = value;
|
|
3657
|
-
this._path =
|
|
3747
|
+
this._path = path2;
|
|
3658
3748
|
this._key = key;
|
|
3659
3749
|
}
|
|
3660
3750
|
get path() {
|
|
@@ -7487,11 +7577,18 @@ var RunsClient = class {
|
|
|
7487
7577
|
this.metrics = cfg.metrics;
|
|
7488
7578
|
this.trace = cfg.trace;
|
|
7489
7579
|
}
|
|
7580
|
+
applyCustomerHeader(headers, customerId) {
|
|
7581
|
+
const trimmed = customerId?.trim();
|
|
7582
|
+
if (trimmed) {
|
|
7583
|
+
headers[CUSTOMER_ID_HEADER] = trimmed;
|
|
7584
|
+
}
|
|
7585
|
+
}
|
|
7490
7586
|
async create(spec, options = {}) {
|
|
7491
7587
|
const metrics = mergeMetrics(this.metrics, options.metrics);
|
|
7492
7588
|
const trace = mergeTrace(this.trace, options.trace);
|
|
7493
7589
|
const authHeaders = await this.auth.authForResponses();
|
|
7494
7590
|
const headers = { ...options.headers || {} };
|
|
7591
|
+
this.applyCustomerHeader(headers, options.customerId);
|
|
7495
7592
|
const payload = { spec };
|
|
7496
7593
|
if (options.idempotencyKey?.trim()) {
|
|
7497
7594
|
payload.options = { idempotency_key: options.idempotencyKey.trim() };
|
|
@@ -7548,10 +7645,12 @@ var RunsClient = class {
|
|
|
7548
7645
|
const metrics = mergeMetrics(this.metrics, options.metrics);
|
|
7549
7646
|
const trace = mergeTrace(this.trace, options.trace);
|
|
7550
7647
|
const authHeaders = await this.auth.authForResponses();
|
|
7551
|
-
const
|
|
7552
|
-
const
|
|
7648
|
+
const path2 = runByIdPath(runId);
|
|
7649
|
+
const headers = { ...options.headers || {} };
|
|
7650
|
+
this.applyCustomerHeader(headers, options.customerId);
|
|
7651
|
+
const out = await this.http.json(path2, {
|
|
7553
7652
|
method: "GET",
|
|
7554
|
-
headers
|
|
7653
|
+
headers,
|
|
7555
7654
|
signal: options.signal,
|
|
7556
7655
|
apiKey: authHeaders.apiKey,
|
|
7557
7656
|
accessToken: authHeaders.accessToken,
|
|
@@ -7560,7 +7659,7 @@ var RunsClient = class {
|
|
|
7560
7659
|
retry: options.retry,
|
|
7561
7660
|
metrics,
|
|
7562
7661
|
trace,
|
|
7563
|
-
context: { method: "GET", path }
|
|
7662
|
+
context: { method: "GET", path: path2 }
|
|
7564
7663
|
});
|
|
7565
7664
|
return {
|
|
7566
7665
|
...out,
|
|
@@ -7584,9 +7683,10 @@ var RunsClient = class {
|
|
|
7584
7683
|
if (options.wait === false) {
|
|
7585
7684
|
params.set("wait", "0");
|
|
7586
7685
|
}
|
|
7587
|
-
const
|
|
7686
|
+
const path2 = params.toString() ? `${basePath}?${params}` : basePath;
|
|
7588
7687
|
const headers = { ...options.headers || {} };
|
|
7589
|
-
|
|
7688
|
+
this.applyCustomerHeader(headers, options.customerId);
|
|
7689
|
+
const resp = await this.http.request(path2, {
|
|
7590
7690
|
method: "GET",
|
|
7591
7691
|
headers,
|
|
7592
7692
|
signal: options.signal,
|
|
@@ -7637,10 +7737,12 @@ var RunsClient = class {
|
|
|
7637
7737
|
const metrics = mergeMetrics(this.metrics, options.metrics);
|
|
7638
7738
|
const trace = mergeTrace(this.trace, options.trace);
|
|
7639
7739
|
const authHeaders = await this.auth.authForResponses();
|
|
7640
|
-
const
|
|
7641
|
-
const
|
|
7740
|
+
const path2 = runToolResultsPath(runId);
|
|
7741
|
+
const headers = { ...options.headers || {} };
|
|
7742
|
+
this.applyCustomerHeader(headers, options.customerId);
|
|
7743
|
+
const out = await this.http.json(path2, {
|
|
7642
7744
|
method: "POST",
|
|
7643
|
-
headers
|
|
7745
|
+
headers,
|
|
7644
7746
|
body: req,
|
|
7645
7747
|
signal: options.signal,
|
|
7646
7748
|
apiKey: authHeaders.apiKey,
|
|
@@ -7650,7 +7752,7 @@ var RunsClient = class {
|
|
|
7650
7752
|
retry: options.retry,
|
|
7651
7753
|
metrics,
|
|
7652
7754
|
trace,
|
|
7653
|
-
context: { method: "POST", path }
|
|
7755
|
+
context: { method: "POST", path: path2 }
|
|
7654
7756
|
});
|
|
7655
7757
|
return out;
|
|
7656
7758
|
}
|
|
@@ -7658,10 +7760,12 @@ var RunsClient = class {
|
|
|
7658
7760
|
const metrics = mergeMetrics(this.metrics, options.metrics);
|
|
7659
7761
|
const trace = mergeTrace(this.trace, options.trace);
|
|
7660
7762
|
const authHeaders = await this.auth.authForResponses();
|
|
7661
|
-
const
|
|
7662
|
-
const
|
|
7763
|
+
const path2 = runPendingToolsPath(runId);
|
|
7764
|
+
const headers = { ...options.headers || {} };
|
|
7765
|
+
this.applyCustomerHeader(headers, options.customerId);
|
|
7766
|
+
const out = await this.http.json(path2, {
|
|
7663
7767
|
method: "GET",
|
|
7664
|
-
headers
|
|
7768
|
+
headers,
|
|
7665
7769
|
signal: options.signal,
|
|
7666
7770
|
apiKey: authHeaders.apiKey,
|
|
7667
7771
|
accessToken: authHeaders.accessToken,
|
|
@@ -7670,7 +7774,7 @@ var RunsClient = class {
|
|
|
7670
7774
|
retry: options.retry,
|
|
7671
7775
|
metrics,
|
|
7672
7776
|
trace,
|
|
7673
|
-
context: { method: "GET", path }
|
|
7777
|
+
context: { method: "GET", path: path2 }
|
|
7674
7778
|
});
|
|
7675
7779
|
return {
|
|
7676
7780
|
...out,
|
|
@@ -7699,12 +7803,17 @@ var WorkflowsClient = class {
|
|
|
7699
7803
|
const metrics = mergeMetrics(this.metrics, options.metrics);
|
|
7700
7804
|
const trace = mergeTrace(this.trace, options.trace);
|
|
7701
7805
|
const authHeaders = await this.auth.authForResponses();
|
|
7806
|
+
const headers = { ...options.headers || {} };
|
|
7807
|
+
const customerId = options.customerId?.trim();
|
|
7808
|
+
if (customerId) {
|
|
7809
|
+
headers[CUSTOMER_ID_HEADER] = customerId;
|
|
7810
|
+
}
|
|
7702
7811
|
try {
|
|
7703
7812
|
const out = await this.http.json(
|
|
7704
7813
|
WORKFLOWS_COMPILE_PATH,
|
|
7705
7814
|
{
|
|
7706
7815
|
method: "POST",
|
|
7707
|
-
headers
|
|
7816
|
+
headers,
|
|
7708
7817
|
body: spec,
|
|
7709
7818
|
signal: options.signal,
|
|
7710
7819
|
apiKey: authHeaders.apiKey,
|
|
@@ -7855,9 +7964,6 @@ var CustomersClient = class {
|
|
|
7855
7964
|
*/
|
|
7856
7965
|
async create(request) {
|
|
7857
7966
|
this.ensureSecretKey();
|
|
7858
|
-
if (!request.tier_id?.trim()) {
|
|
7859
|
-
throw new ConfigError("tier_id is required");
|
|
7860
|
-
}
|
|
7861
7967
|
if (!request.external_id?.trim()) {
|
|
7862
7968
|
throw new ConfigError("external_id is required");
|
|
7863
7969
|
}
|
|
@@ -7898,9 +8004,6 @@ var CustomersClient = class {
|
|
|
7898
8004
|
*/
|
|
7899
8005
|
async upsert(request) {
|
|
7900
8006
|
this.ensureSecretKey();
|
|
7901
|
-
if (!request.tier_id?.trim()) {
|
|
7902
|
-
throw new ConfigError("tier_id is required");
|
|
7903
|
-
}
|
|
7904
8007
|
if (!request.external_id?.trim()) {
|
|
7905
8008
|
throw new ConfigError("external_id is required");
|
|
7906
8009
|
}
|
|
@@ -7918,7 +8021,7 @@ var CustomersClient = class {
|
|
|
7918
8021
|
return response.customer;
|
|
7919
8022
|
}
|
|
7920
8023
|
/**
|
|
7921
|
-
* Link
|
|
8024
|
+
* Link a customer identity (provider + subject) to a customer found by email.
|
|
7922
8025
|
* Used when a customer subscribes via Stripe Checkout (email only) and later authenticates to the app.
|
|
7923
8026
|
*
|
|
7924
8027
|
* This is a user self-service operation that works with publishable keys,
|
|
@@ -7943,12 +8046,11 @@ var CustomersClient = class {
|
|
|
7943
8046
|
if (!request.subject?.trim()) {
|
|
7944
8047
|
throw new ConfigError("subject is required");
|
|
7945
8048
|
}
|
|
7946
|
-
|
|
8049
|
+
await this.http.request("/customers/claim", {
|
|
7947
8050
|
method: "POST",
|
|
7948
8051
|
body: request,
|
|
7949
8052
|
apiKey: this.apiKey
|
|
7950
8053
|
});
|
|
7951
|
-
return response.customer;
|
|
7952
8054
|
}
|
|
7953
8055
|
/**
|
|
7954
8056
|
* Delete a customer by ID.
|
|
@@ -7964,18 +8066,21 @@ var CustomersClient = class {
|
|
|
7964
8066
|
});
|
|
7965
8067
|
}
|
|
7966
8068
|
/**
|
|
7967
|
-
* Create a Stripe checkout session for a customer.
|
|
8069
|
+
* Create a Stripe checkout session for a customer subscription.
|
|
7968
8070
|
*/
|
|
7969
|
-
async
|
|
8071
|
+
async subscribe(customerId, request) {
|
|
7970
8072
|
this.ensureSecretKey();
|
|
7971
8073
|
if (!customerId?.trim()) {
|
|
7972
8074
|
throw new ConfigError("customerId is required");
|
|
7973
8075
|
}
|
|
8076
|
+
if (!request.tier_id?.trim()) {
|
|
8077
|
+
throw new ConfigError("tier_id is required");
|
|
8078
|
+
}
|
|
7974
8079
|
if (!request.success_url?.trim() || !request.cancel_url?.trim()) {
|
|
7975
8080
|
throw new ConfigError("success_url and cancel_url are required");
|
|
7976
8081
|
}
|
|
7977
8082
|
return await this.http.json(
|
|
7978
|
-
`/customers/${customerId}/
|
|
8083
|
+
`/customers/${customerId}/subscribe`,
|
|
7979
8084
|
{
|
|
7980
8085
|
method: "POST",
|
|
7981
8086
|
body: request,
|
|
@@ -7984,20 +8089,34 @@ var CustomersClient = class {
|
|
|
7984
8089
|
);
|
|
7985
8090
|
}
|
|
7986
8091
|
/**
|
|
7987
|
-
* Get the subscription
|
|
8092
|
+
* Get the subscription details for a customer.
|
|
7988
8093
|
*/
|
|
7989
8094
|
async getSubscription(customerId) {
|
|
7990
8095
|
this.ensureSecretKey();
|
|
7991
8096
|
if (!customerId?.trim()) {
|
|
7992
8097
|
throw new ConfigError("customerId is required");
|
|
7993
8098
|
}
|
|
7994
|
-
|
|
8099
|
+
const response = await this.http.json(
|
|
7995
8100
|
`/customers/${customerId}/subscription`,
|
|
7996
8101
|
{
|
|
7997
8102
|
method: "GET",
|
|
7998
8103
|
apiKey: this.apiKey
|
|
7999
8104
|
}
|
|
8000
8105
|
);
|
|
8106
|
+
return response.subscription;
|
|
8107
|
+
}
|
|
8108
|
+
/**
|
|
8109
|
+
* Cancel a customer's subscription at period end.
|
|
8110
|
+
*/
|
|
8111
|
+
async unsubscribe(customerId) {
|
|
8112
|
+
this.ensureSecretKey();
|
|
8113
|
+
if (!customerId?.trim()) {
|
|
8114
|
+
throw new ConfigError("customerId is required");
|
|
8115
|
+
}
|
|
8116
|
+
await this.http.request(`/customers/${customerId}/subscription`, {
|
|
8117
|
+
method: "DELETE",
|
|
8118
|
+
apiKey: this.apiKey
|
|
8119
|
+
});
|
|
8001
8120
|
}
|
|
8002
8121
|
};
|
|
8003
8122
|
|
|
@@ -8099,903 +8218,1908 @@ var ModelsClient = class {
|
|
|
8099
8218
|
if (params.capability) {
|
|
8100
8219
|
qs.set("capability", params.capability);
|
|
8101
8220
|
}
|
|
8102
|
-
const
|
|
8103
|
-
const resp = await this.http.json(
|
|
8221
|
+
const path2 = qs.toString() ? `/models?${qs.toString()}` : "/models";
|
|
8222
|
+
const resp = await this.http.json(path2, { method: "GET" });
|
|
8104
8223
|
return resp.models;
|
|
8105
8224
|
}
|
|
8106
8225
|
};
|
|
8107
8226
|
|
|
8108
|
-
// src/
|
|
8109
|
-
var
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8227
|
+
// src/images.ts
|
|
8228
|
+
var IMAGES_PATH = "/images/generate";
|
|
8229
|
+
var ImagesClient = class {
|
|
8230
|
+
constructor(http, auth) {
|
|
8231
|
+
this.http = http;
|
|
8232
|
+
this.auth = auth;
|
|
8233
|
+
}
|
|
8234
|
+
/**
|
|
8235
|
+
* Generate images from a text prompt.
|
|
8236
|
+
*
|
|
8237
|
+
* By default, returns URLs (requires storage configuration).
|
|
8238
|
+
* Use response_format: "b64_json" for testing without storage.
|
|
8239
|
+
*
|
|
8240
|
+
* @param request - Image generation request (model optional if tier defines default)
|
|
8241
|
+
* @returns Generated images with URLs or base64 data
|
|
8242
|
+
* @throws {Error} If prompt is empty
|
|
8243
|
+
*/
|
|
8244
|
+
async generate(request) {
|
|
8245
|
+
if (!request.prompt?.trim()) {
|
|
8246
|
+
throw new Error("prompt is required");
|
|
8116
8247
|
}
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
this.retry = normalizeRetryConfig(cfg.retry);
|
|
8125
|
-
this.defaultHeaders = normalizeHeaders(cfg.defaultHeaders);
|
|
8126
|
-
this.metrics = cfg.metrics;
|
|
8127
|
-
this.trace = cfg.trace;
|
|
8248
|
+
const auth = await this.auth.authForResponses();
|
|
8249
|
+
return await this.http.json(IMAGES_PATH, {
|
|
8250
|
+
method: "POST",
|
|
8251
|
+
body: request,
|
|
8252
|
+
apiKey: auth.apiKey,
|
|
8253
|
+
accessToken: auth.accessToken
|
|
8254
|
+
});
|
|
8128
8255
|
}
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8134
|
-
|
|
8256
|
+
};
|
|
8257
|
+
|
|
8258
|
+
// src/sessions/types.ts
|
|
8259
|
+
function asSessionId(value) {
|
|
8260
|
+
return value;
|
|
8261
|
+
}
|
|
8262
|
+
function generateSessionId() {
|
|
8263
|
+
return crypto.randomUUID();
|
|
8264
|
+
}
|
|
8265
|
+
|
|
8266
|
+
// src/sessions/stores/memory_store.ts
|
|
8267
|
+
var MemorySessionStore = class {
|
|
8268
|
+
constructor() {
|
|
8269
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
8270
|
+
}
|
|
8271
|
+
async load(id) {
|
|
8272
|
+
const state = this.sessions.get(id);
|
|
8273
|
+
if (!state) return null;
|
|
8274
|
+
return structuredClone(state);
|
|
8275
|
+
}
|
|
8276
|
+
async save(state) {
|
|
8277
|
+
this.sessions.set(state.id, structuredClone(state));
|
|
8278
|
+
}
|
|
8279
|
+
async delete(id) {
|
|
8280
|
+
this.sessions.delete(id);
|
|
8281
|
+
}
|
|
8282
|
+
async list() {
|
|
8283
|
+
return Array.from(this.sessions.keys());
|
|
8284
|
+
}
|
|
8285
|
+
async close() {
|
|
8286
|
+
this.sessions.clear();
|
|
8287
|
+
}
|
|
8288
|
+
/**
|
|
8289
|
+
* Get the number of sessions in the store.
|
|
8290
|
+
* Useful for testing.
|
|
8291
|
+
*/
|
|
8292
|
+
get size() {
|
|
8293
|
+
return this.sessions.size;
|
|
8294
|
+
}
|
|
8295
|
+
};
|
|
8296
|
+
function createMemorySessionStore() {
|
|
8297
|
+
return new MemorySessionStore();
|
|
8298
|
+
}
|
|
8299
|
+
|
|
8300
|
+
// src/sessions/local_session.ts
|
|
8301
|
+
var LocalSession = class _LocalSession {
|
|
8302
|
+
constructor(client, store, options, existingState) {
|
|
8303
|
+
this.type = "local";
|
|
8304
|
+
this.messages = [];
|
|
8305
|
+
this.artifacts = /* @__PURE__ */ new Map();
|
|
8306
|
+
this.nextSeq = 1;
|
|
8307
|
+
this.currentEvents = [];
|
|
8308
|
+
this.currentUsage = {
|
|
8309
|
+
inputTokens: 0,
|
|
8310
|
+
outputTokens: 0,
|
|
8311
|
+
totalTokens: 0,
|
|
8312
|
+
llmCalls: 0,
|
|
8313
|
+
toolCalls: 0
|
|
8314
|
+
};
|
|
8315
|
+
this.client = client;
|
|
8316
|
+
this.store = store;
|
|
8317
|
+
this.toolRegistry = options.toolRegistry;
|
|
8318
|
+
this.defaultModel = options.defaultModel;
|
|
8319
|
+
this.defaultProvider = options.defaultProvider;
|
|
8320
|
+
this.defaultTools = options.defaultTools;
|
|
8321
|
+
this.metadata = options.metadata || {};
|
|
8322
|
+
if (existingState) {
|
|
8323
|
+
this.id = existingState.id;
|
|
8324
|
+
this.messages = existingState.messages.map((m) => ({
|
|
8325
|
+
...m,
|
|
8326
|
+
createdAt: new Date(m.createdAt)
|
|
8327
|
+
}));
|
|
8328
|
+
this.artifacts = new Map(Object.entries(existingState.artifacts));
|
|
8329
|
+
this.nextSeq = this.messages.length + 1;
|
|
8330
|
+
this.createdAt = new Date(existingState.createdAt);
|
|
8331
|
+
this.updatedAt = new Date(existingState.updatedAt);
|
|
8332
|
+
} else {
|
|
8333
|
+
this.id = options.sessionId || generateSessionId();
|
|
8334
|
+
this.createdAt = /* @__PURE__ */ new Date();
|
|
8335
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
8135
8336
|
}
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8337
|
+
}
|
|
8338
|
+
/**
|
|
8339
|
+
* Create a new local session.
|
|
8340
|
+
*
|
|
8341
|
+
* @param client - ModelRelay client
|
|
8342
|
+
* @param options - Session configuration
|
|
8343
|
+
* @returns A new LocalSession instance
|
|
8344
|
+
*/
|
|
8345
|
+
static create(client, options = {}) {
|
|
8346
|
+
const store = createStore(options.persistence || "memory", options.storagePath);
|
|
8347
|
+
return new _LocalSession(client, store, options);
|
|
8348
|
+
}
|
|
8349
|
+
/**
|
|
8350
|
+
* Resume an existing session from storage.
|
|
8351
|
+
*
|
|
8352
|
+
* @param client - ModelRelay client
|
|
8353
|
+
* @param sessionId - ID of the session to resume
|
|
8354
|
+
* @param options - Session configuration (must match original persistence settings)
|
|
8355
|
+
* @returns The resumed LocalSession, or null if not found
|
|
8356
|
+
*/
|
|
8357
|
+
static async resume(client, sessionId, options = {}) {
|
|
8358
|
+
const id = typeof sessionId === "string" ? asSessionId(sessionId) : sessionId;
|
|
8359
|
+
const store = createStore(options.persistence || "memory", options.storagePath);
|
|
8360
|
+
const state = await store.load(id);
|
|
8361
|
+
if (!state) {
|
|
8362
|
+
await store.close();
|
|
8363
|
+
return null;
|
|
8364
|
+
}
|
|
8365
|
+
return new _LocalSession(client, store, options, state);
|
|
8366
|
+
}
|
|
8367
|
+
get history() {
|
|
8368
|
+
return this.messages;
|
|
8369
|
+
}
|
|
8370
|
+
async run(prompt, options = {}) {
|
|
8371
|
+
const userMessage = this.addMessage({
|
|
8372
|
+
type: "message",
|
|
8373
|
+
role: "user",
|
|
8374
|
+
content: [{ type: "text", text: prompt }]
|
|
8375
|
+
});
|
|
8376
|
+
this.currentEvents = [];
|
|
8377
|
+
this.currentUsage = {
|
|
8378
|
+
inputTokens: 0,
|
|
8379
|
+
outputTokens: 0,
|
|
8380
|
+
totalTokens: 0,
|
|
8381
|
+
llmCalls: 0,
|
|
8382
|
+
toolCalls: 0
|
|
8144
8383
|
};
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8384
|
+
this.currentRunId = void 0;
|
|
8385
|
+
this.currentNodeId = void 0;
|
|
8386
|
+
this.currentWaiting = void 0;
|
|
8387
|
+
try {
|
|
8388
|
+
const input = this.buildInput();
|
|
8389
|
+
const tools = mergeTools(this.defaultTools, options.tools);
|
|
8390
|
+
const spec = {
|
|
8391
|
+
kind: "workflow.v0",
|
|
8392
|
+
name: `session-${this.id}-turn-${this.nextSeq}`,
|
|
8393
|
+
nodes: [
|
|
8394
|
+
{
|
|
8395
|
+
id: "main",
|
|
8396
|
+
type: "llm.responses",
|
|
8397
|
+
input: {
|
|
8398
|
+
request: {
|
|
8399
|
+
provider: options.provider || this.defaultProvider,
|
|
8400
|
+
model: options.model || this.defaultModel,
|
|
8401
|
+
input,
|
|
8402
|
+
tools
|
|
8403
|
+
},
|
|
8404
|
+
tool_execution: this.toolRegistry ? { mode: "client" } : void 0
|
|
8405
|
+
}
|
|
8406
|
+
}
|
|
8407
|
+
],
|
|
8408
|
+
outputs: [{ name: "result", from: "main" }]
|
|
8409
|
+
};
|
|
8410
|
+
const run = await this.client.runs.create(spec, {
|
|
8411
|
+
customerId: options.customerId
|
|
8412
|
+
});
|
|
8413
|
+
this.currentRunId = run.run_id;
|
|
8414
|
+
return await this.processRunEvents(options.signal);
|
|
8415
|
+
} catch (err) {
|
|
8416
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
8417
|
+
return {
|
|
8418
|
+
status: "error",
|
|
8419
|
+
error: error.message,
|
|
8420
|
+
runId: this.currentRunId || parseRunId("unknown"),
|
|
8421
|
+
usage: this.currentUsage,
|
|
8422
|
+
events: this.currentEvents
|
|
8423
|
+
};
|
|
8154
8424
|
}
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
headers.set("Content-Type", "application/json");
|
|
8425
|
+
}
|
|
8426
|
+
async submitToolResults(results) {
|
|
8427
|
+
if (!this.currentRunId || !this.currentNodeId || !this.currentWaiting) {
|
|
8428
|
+
throw new Error("No pending tool calls to submit results for");
|
|
8160
8429
|
}
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8430
|
+
await this.client.runs.submitToolResults(this.currentRunId, {
|
|
8431
|
+
node_id: this.currentNodeId,
|
|
8432
|
+
step: this.currentWaiting.step,
|
|
8433
|
+
request_id: this.currentWaiting.request_id,
|
|
8434
|
+
results: results.map((r) => ({
|
|
8435
|
+
tool_call_id: r.toolCallId,
|
|
8436
|
+
name: r.toolName,
|
|
8437
|
+
output: r.error ? `Error: ${r.error}` : typeof r.result === "string" ? r.result : JSON.stringify(r.result)
|
|
8438
|
+
}))
|
|
8439
|
+
});
|
|
8440
|
+
this.currentWaiting = void 0;
|
|
8441
|
+
return await this.processRunEvents();
|
|
8442
|
+
}
|
|
8443
|
+
getArtifacts() {
|
|
8444
|
+
return new Map(this.artifacts);
|
|
8445
|
+
}
|
|
8446
|
+
async close() {
|
|
8447
|
+
await this.persist();
|
|
8448
|
+
await this.store.close();
|
|
8449
|
+
}
|
|
8450
|
+
// ============================================================================
|
|
8451
|
+
// Private Methods
|
|
8452
|
+
// ============================================================================
|
|
8453
|
+
addMessage(input, runId) {
|
|
8454
|
+
const message = {
|
|
8455
|
+
...input,
|
|
8456
|
+
seq: this.nextSeq++,
|
|
8457
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
8458
|
+
runId
|
|
8459
|
+
};
|
|
8460
|
+
this.messages.push(message);
|
|
8461
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
8462
|
+
return message;
|
|
8463
|
+
}
|
|
8464
|
+
buildInput() {
|
|
8465
|
+
return this.messages.map((m) => ({
|
|
8466
|
+
type: m.type,
|
|
8467
|
+
role: m.role,
|
|
8468
|
+
content: m.content,
|
|
8469
|
+
toolCalls: m.toolCalls,
|
|
8470
|
+
toolCallId: m.toolCallId
|
|
8471
|
+
}));
|
|
8472
|
+
}
|
|
8473
|
+
async processRunEvents(signal) {
|
|
8474
|
+
if (!this.currentRunId) {
|
|
8475
|
+
throw new Error("No current run");
|
|
8165
8476
|
}
|
|
8166
|
-
const
|
|
8167
|
-
|
|
8168
|
-
|
|
8477
|
+
const eventStream = await this.client.runs.events(this.currentRunId, {
|
|
8478
|
+
afterSeq: this.currentEvents.length
|
|
8479
|
+
});
|
|
8480
|
+
for await (const event of eventStream) {
|
|
8481
|
+
if (signal?.aborted) {
|
|
8482
|
+
return {
|
|
8483
|
+
status: "canceled",
|
|
8484
|
+
runId: this.currentRunId,
|
|
8485
|
+
usage: this.currentUsage,
|
|
8486
|
+
events: this.currentEvents
|
|
8487
|
+
};
|
|
8488
|
+
}
|
|
8489
|
+
this.currentEvents.push(event);
|
|
8490
|
+
switch (event.type) {
|
|
8491
|
+
case "node_llm_call":
|
|
8492
|
+
this.currentUsage = {
|
|
8493
|
+
...this.currentUsage,
|
|
8494
|
+
llmCalls: this.currentUsage.llmCalls + 1,
|
|
8495
|
+
inputTokens: this.currentUsage.inputTokens + (event.llm_call.usage?.input_tokens || 0),
|
|
8496
|
+
outputTokens: this.currentUsage.outputTokens + (event.llm_call.usage?.output_tokens || 0),
|
|
8497
|
+
totalTokens: this.currentUsage.totalTokens + (event.llm_call.usage?.total_tokens || 0)
|
|
8498
|
+
};
|
|
8499
|
+
break;
|
|
8500
|
+
case "node_tool_call":
|
|
8501
|
+
this.currentUsage = {
|
|
8502
|
+
...this.currentUsage,
|
|
8503
|
+
toolCalls: this.currentUsage.toolCalls + 1
|
|
8504
|
+
};
|
|
8505
|
+
break;
|
|
8506
|
+
case "node_waiting":
|
|
8507
|
+
this.currentNodeId = event.node_id;
|
|
8508
|
+
this.currentWaiting = event.waiting;
|
|
8509
|
+
if (this.toolRegistry) {
|
|
8510
|
+
const results = await this.executeTools(event.waiting.pending_tool_calls);
|
|
8511
|
+
return await this.submitToolResults(results);
|
|
8512
|
+
}
|
|
8513
|
+
return {
|
|
8514
|
+
status: "waiting_for_tools",
|
|
8515
|
+
pendingTools: event.waiting.pending_tool_calls.map((tc) => ({
|
|
8516
|
+
toolCallId: tc.tool_call_id,
|
|
8517
|
+
name: tc.name,
|
|
8518
|
+
arguments: tc.arguments
|
|
8519
|
+
})),
|
|
8520
|
+
runId: this.currentRunId,
|
|
8521
|
+
usage: this.currentUsage,
|
|
8522
|
+
events: this.currentEvents
|
|
8523
|
+
};
|
|
8524
|
+
case "run_completed":
|
|
8525
|
+
const runState = await this.client.runs.get(this.currentRunId);
|
|
8526
|
+
const output = extractTextOutput(runState.outputs || {});
|
|
8527
|
+
if (output) {
|
|
8528
|
+
this.addMessage(
|
|
8529
|
+
{
|
|
8530
|
+
type: "message",
|
|
8531
|
+
role: "assistant",
|
|
8532
|
+
content: [{ type: "text", text: output }]
|
|
8533
|
+
},
|
|
8534
|
+
this.currentRunId
|
|
8535
|
+
);
|
|
8536
|
+
}
|
|
8537
|
+
await this.persist();
|
|
8538
|
+
return {
|
|
8539
|
+
status: "complete",
|
|
8540
|
+
output,
|
|
8541
|
+
runId: this.currentRunId,
|
|
8542
|
+
usage: this.currentUsage,
|
|
8543
|
+
events: this.currentEvents
|
|
8544
|
+
};
|
|
8545
|
+
case "run_failed":
|
|
8546
|
+
return {
|
|
8547
|
+
status: "error",
|
|
8548
|
+
error: event.error.message,
|
|
8549
|
+
runId: this.currentRunId,
|
|
8550
|
+
usage: this.currentUsage,
|
|
8551
|
+
events: this.currentEvents
|
|
8552
|
+
};
|
|
8553
|
+
case "run_canceled":
|
|
8554
|
+
return {
|
|
8555
|
+
status: "canceled",
|
|
8556
|
+
error: event.error.message,
|
|
8557
|
+
runId: this.currentRunId,
|
|
8558
|
+
usage: this.currentUsage,
|
|
8559
|
+
events: this.currentEvents
|
|
8560
|
+
};
|
|
8561
|
+
}
|
|
8169
8562
|
}
|
|
8170
|
-
|
|
8171
|
-
|
|
8563
|
+
return {
|
|
8564
|
+
status: "error",
|
|
8565
|
+
error: "Run event stream ended unexpectedly",
|
|
8566
|
+
runId: this.currentRunId,
|
|
8567
|
+
usage: this.currentUsage,
|
|
8568
|
+
events: this.currentEvents
|
|
8569
|
+
};
|
|
8570
|
+
}
|
|
8571
|
+
async executeTools(pendingTools) {
|
|
8572
|
+
if (!this.toolRegistry) {
|
|
8573
|
+
throw new Error("No tool registry configured");
|
|
8172
8574
|
}
|
|
8173
|
-
const
|
|
8174
|
-
const
|
|
8175
|
-
const retryCfg = normalizeRetryConfig(
|
|
8176
|
-
options.retry === void 0 ? this.retry : options.retry
|
|
8177
|
-
);
|
|
8178
|
-
const attempts = retryCfg ? Math.max(1, retryCfg.maxAttempts) : 1;
|
|
8179
|
-
let lastError;
|
|
8180
|
-
let lastStatus;
|
|
8181
|
-
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
8182
|
-
let connectTimedOut = false;
|
|
8183
|
-
let requestTimedOut = false;
|
|
8184
|
-
const connectController = connectTimeoutMs && connectTimeoutMs > 0 ? new AbortController() : void 0;
|
|
8185
|
-
const requestController = timeoutMs && timeoutMs > 0 ? new AbortController() : void 0;
|
|
8186
|
-
const signal = mergeSignals(
|
|
8187
|
-
options.signal,
|
|
8188
|
-
connectController?.signal,
|
|
8189
|
-
requestController?.signal
|
|
8190
|
-
);
|
|
8191
|
-
const connectTimer = connectController && setTimeout(() => {
|
|
8192
|
-
connectTimedOut = true;
|
|
8193
|
-
connectController.abort(
|
|
8194
|
-
new DOMException("connect timeout", "AbortError")
|
|
8195
|
-
);
|
|
8196
|
-
}, connectTimeoutMs);
|
|
8197
|
-
const requestTimer = requestController && setTimeout(() => {
|
|
8198
|
-
requestTimedOut = true;
|
|
8199
|
-
requestController.abort(
|
|
8200
|
-
new DOMException("timeout", "AbortError")
|
|
8201
|
-
);
|
|
8202
|
-
}, timeoutMs);
|
|
8575
|
+
const results = [];
|
|
8576
|
+
for (const pending of pendingTools) {
|
|
8203
8577
|
try {
|
|
8204
|
-
const
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
if (connectTimer) {
|
|
8211
|
-
clearTimeout(connectTimer);
|
|
8212
|
-
}
|
|
8213
|
-
if (!response.ok) {
|
|
8214
|
-
const shouldRetry = retryCfg && shouldRetryStatus(
|
|
8215
|
-
response.status,
|
|
8216
|
-
method,
|
|
8217
|
-
retryCfg.retryPost
|
|
8218
|
-
) && attempt < attempts;
|
|
8219
|
-
if (shouldRetry) {
|
|
8220
|
-
lastStatus = response.status;
|
|
8221
|
-
await backoff(attempt, retryCfg);
|
|
8222
|
-
continue;
|
|
8578
|
+
const result = await this.toolRegistry.execute({
|
|
8579
|
+
id: pending.tool_call_id,
|
|
8580
|
+
type: "function",
|
|
8581
|
+
function: {
|
|
8582
|
+
name: pending.name,
|
|
8583
|
+
arguments: pending.arguments
|
|
8223
8584
|
}
|
|
8224
|
-
const retries = buildRetryMetadata(attempt, response.status, lastError);
|
|
8225
|
-
const finishedCtx2 = withRequestId(context, response.headers);
|
|
8226
|
-
recordHttpMetrics(metrics, trace, start, retries, {
|
|
8227
|
-
status: response.status,
|
|
8228
|
-
context: finishedCtx2
|
|
8229
|
-
});
|
|
8230
|
-
throw options.raw ? await parseErrorResponse(response, retries) : await parseErrorResponse(response, retries);
|
|
8231
|
-
}
|
|
8232
|
-
const finishedCtx = withRequestId(context, response.headers);
|
|
8233
|
-
recordHttpMetrics(metrics, trace, start, void 0, {
|
|
8234
|
-
status: response.status,
|
|
8235
|
-
context: finishedCtx
|
|
8236
8585
|
});
|
|
8237
|
-
|
|
8586
|
+
results.push(result);
|
|
8238
8587
|
} catch (err) {
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
});
|
|
8247
|
-
throw err;
|
|
8248
|
-
}
|
|
8249
|
-
const transportKind = classifyTransportErrorKind(
|
|
8250
|
-
err,
|
|
8251
|
-
connectTimedOut,
|
|
8252
|
-
requestTimedOut
|
|
8253
|
-
);
|
|
8254
|
-
const shouldRetry = retryCfg && isRetryableError(err, transportKind) && (method !== "POST" || retryCfg.retryPost) && attempt < attempts;
|
|
8255
|
-
if (!shouldRetry) {
|
|
8256
|
-
const retries = buildRetryMetadata(
|
|
8257
|
-
attempt,
|
|
8258
|
-
lastStatus,
|
|
8259
|
-
err instanceof Error ? err.message : String(err)
|
|
8260
|
-
);
|
|
8261
|
-
recordHttpMetrics(metrics, trace, start, retries, {
|
|
8262
|
-
error: err,
|
|
8263
|
-
context
|
|
8264
|
-
});
|
|
8265
|
-
throw toTransportError(err, transportKind, retries);
|
|
8266
|
-
}
|
|
8267
|
-
lastError = err;
|
|
8268
|
-
await backoff(attempt, retryCfg);
|
|
8269
|
-
} finally {
|
|
8270
|
-
if (connectTimer) {
|
|
8271
|
-
clearTimeout(connectTimer);
|
|
8272
|
-
}
|
|
8273
|
-
if (requestTimer) {
|
|
8274
|
-
clearTimeout(requestTimer);
|
|
8275
|
-
}
|
|
8588
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
8589
|
+
results.push({
|
|
8590
|
+
toolCallId: pending.tool_call_id,
|
|
8591
|
+
toolName: pending.name,
|
|
8592
|
+
result: null,
|
|
8593
|
+
error: error.message
|
|
8594
|
+
});
|
|
8276
8595
|
}
|
|
8277
8596
|
}
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
}
|
|
8292
|
-
|
|
8293
|
-
return void 0;
|
|
8294
|
-
}
|
|
8295
|
-
try {
|
|
8296
|
-
return await response.json();
|
|
8297
|
-
} catch (err) {
|
|
8298
|
-
throw new APIError("failed to parse response JSON", {
|
|
8299
|
-
status: response.status,
|
|
8300
|
-
data: err
|
|
8301
|
-
});
|
|
8302
|
-
}
|
|
8597
|
+
return results;
|
|
8598
|
+
}
|
|
8599
|
+
async persist() {
|
|
8600
|
+
const state = {
|
|
8601
|
+
id: this.id,
|
|
8602
|
+
messages: this.messages.map((m) => ({
|
|
8603
|
+
...m,
|
|
8604
|
+
createdAt: m.createdAt
|
|
8605
|
+
})),
|
|
8606
|
+
artifacts: Object.fromEntries(this.artifacts),
|
|
8607
|
+
metadata: this.metadata,
|
|
8608
|
+
createdAt: this.createdAt.toISOString(),
|
|
8609
|
+
updatedAt: this.updatedAt.toISOString()
|
|
8610
|
+
};
|
|
8611
|
+
await this.store.save(state);
|
|
8303
8612
|
}
|
|
8304
8613
|
};
|
|
8305
|
-
function
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
const trimmed = value.trim();
|
|
8316
|
-
if (trimmed.endsWith("/")) {
|
|
8317
|
-
return trimmed.slice(0, -1);
|
|
8614
|
+
function createStore(persistence, storagePath) {
|
|
8615
|
+
switch (persistence) {
|
|
8616
|
+
case "memory":
|
|
8617
|
+
return createMemorySessionStore();
|
|
8618
|
+
case "file":
|
|
8619
|
+
throw new Error("File persistence not yet implemented");
|
|
8620
|
+
case "sqlite":
|
|
8621
|
+
throw new Error("SQLite persistence not yet implemented");
|
|
8622
|
+
default:
|
|
8623
|
+
throw new Error(`Unknown persistence mode: ${persistence}`);
|
|
8318
8624
|
}
|
|
8319
|
-
return trimmed;
|
|
8320
|
-
}
|
|
8321
|
-
function isValidHttpUrl(value) {
|
|
8322
|
-
return /^https?:\/\//i.test(value);
|
|
8323
8625
|
}
|
|
8324
|
-
function
|
|
8325
|
-
if (
|
|
8326
|
-
|
|
8327
|
-
return
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
}
|
|
8334
|
-
function shouldRetryStatus(status, method, retryPost) {
|
|
8335
|
-
if (status === 408 || status === 429) {
|
|
8336
|
-
return method !== "POST" || retryPost;
|
|
8337
|
-
}
|
|
8338
|
-
if (status >= 500 && status < 600) {
|
|
8339
|
-
return method !== "POST" || retryPost;
|
|
8626
|
+
function mergeTools(defaults, overrides) {
|
|
8627
|
+
if (!defaults && !overrides) return void 0;
|
|
8628
|
+
if (!defaults) return overrides;
|
|
8629
|
+
if (!overrides) return defaults;
|
|
8630
|
+
const merged = /* @__PURE__ */ new Map();
|
|
8631
|
+
for (const tool of defaults) {
|
|
8632
|
+
if (tool.type === "function" && tool.function) {
|
|
8633
|
+
merged.set(tool.function.name, tool);
|
|
8634
|
+
}
|
|
8340
8635
|
}
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
function
|
|
8344
|
-
if (!err) return false;
|
|
8345
|
-
if (kind === "timeout" || kind === "connect") return true;
|
|
8346
|
-
return err instanceof DOMException || err instanceof TypeError;
|
|
8347
|
-
}
|
|
8348
|
-
function backoff(attempt, cfg) {
|
|
8349
|
-
const exp = Math.max(0, attempt - 1);
|
|
8350
|
-
const base = cfg.baseBackoffMs * Math.pow(2, Math.min(exp, 10));
|
|
8351
|
-
const capped = Math.min(base, cfg.maxBackoffMs);
|
|
8352
|
-
const jitter = 0.5 + Math.random();
|
|
8353
|
-
const delay = Math.min(cfg.maxBackoffMs, capped * jitter);
|
|
8354
|
-
if (delay <= 0) return Promise.resolve();
|
|
8355
|
-
return new Promise((resolve) => setTimeout(resolve, delay));
|
|
8356
|
-
}
|
|
8357
|
-
function mergeSignals(...signals) {
|
|
8358
|
-
const active = signals.filter(Boolean);
|
|
8359
|
-
if (active.length === 0) return void 0;
|
|
8360
|
-
if (active.length === 1) return active[0];
|
|
8361
|
-
const controller = new AbortController();
|
|
8362
|
-
for (const src of active) {
|
|
8363
|
-
if (src.aborted) {
|
|
8364
|
-
controller.abort(src.reason);
|
|
8365
|
-
break;
|
|
8636
|
+
for (const tool of overrides) {
|
|
8637
|
+
if (tool.type === "function" && tool.function) {
|
|
8638
|
+
merged.set(tool.function.name, tool);
|
|
8366
8639
|
}
|
|
8367
|
-
src.addEventListener(
|
|
8368
|
-
"abort",
|
|
8369
|
-
() => controller.abort(src.reason),
|
|
8370
|
-
{ once: true }
|
|
8371
|
-
);
|
|
8372
8640
|
}
|
|
8373
|
-
return
|
|
8641
|
+
return Array.from(merged.values());
|
|
8374
8642
|
}
|
|
8375
|
-
function
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8643
|
+
function extractTextOutput(outputs) {
|
|
8644
|
+
const result = outputs.result;
|
|
8645
|
+
if (typeof result === "string") return result;
|
|
8646
|
+
if (result && typeof result === "object") {
|
|
8647
|
+
const resp = result;
|
|
8648
|
+
if (Array.isArray(resp.output)) {
|
|
8649
|
+
const textParts = resp.output.filter((item) => item?.type === "message" && item?.role === "assistant").flatMap(
|
|
8650
|
+
(item) => (item.content || []).filter((c) => c?.type === "text").map((c) => c.text)
|
|
8651
|
+
);
|
|
8652
|
+
if (textParts.length > 0) {
|
|
8653
|
+
return textParts.join("\n");
|
|
8654
|
+
}
|
|
8655
|
+
}
|
|
8656
|
+
if (Array.isArray(resp.content)) {
|
|
8657
|
+
const textParts = resp.content.filter((c) => c?.type === "text").map((c) => c.text);
|
|
8658
|
+
if (textParts.length > 0) {
|
|
8659
|
+
return textParts.join("\n");
|
|
8660
|
+
}
|
|
8384
8661
|
}
|
|
8385
8662
|
}
|
|
8386
|
-
return
|
|
8663
|
+
return void 0;
|
|
8387
8664
|
}
|
|
8388
|
-
function
|
|
8389
|
-
|
|
8390
|
-
return {
|
|
8391
|
-
attempts: attempt,
|
|
8392
|
-
lastStatus,
|
|
8393
|
-
lastError: typeof lastError === "string" ? lastError : lastError instanceof Error ? lastError.message : lastError ? String(lastError) : void 0
|
|
8394
|
-
};
|
|
8665
|
+
function createLocalSession(client, options = {}) {
|
|
8666
|
+
return LocalSession.create(client, options);
|
|
8395
8667
|
}
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
|
|
8668
|
+
|
|
8669
|
+
// src/sessions/remote_session.ts
|
|
8670
|
+
var RemoteSession = class _RemoteSession {
|
|
8671
|
+
constructor(client, http, sessionData, options = {}) {
|
|
8672
|
+
this.type = "remote";
|
|
8673
|
+
this.messages = [];
|
|
8674
|
+
this.artifacts = /* @__PURE__ */ new Map();
|
|
8675
|
+
this.nextSeq = 1;
|
|
8676
|
+
this.currentEvents = [];
|
|
8677
|
+
this.currentUsage = {
|
|
8678
|
+
inputTokens: 0,
|
|
8679
|
+
outputTokens: 0,
|
|
8680
|
+
totalTokens: 0,
|
|
8681
|
+
llmCalls: 0,
|
|
8682
|
+
toolCalls: 0
|
|
8683
|
+
};
|
|
8684
|
+
this.client = client;
|
|
8685
|
+
this.http = http;
|
|
8686
|
+
this.id = asSessionId(sessionData.id);
|
|
8687
|
+
this.metadata = sessionData.metadata;
|
|
8688
|
+
this.endUserId = sessionData.end_user_id || options.endUserId;
|
|
8689
|
+
this.createdAt = new Date(sessionData.created_at);
|
|
8690
|
+
this.updatedAt = new Date(sessionData.updated_at);
|
|
8691
|
+
this.toolRegistry = options.toolRegistry;
|
|
8692
|
+
this.defaultModel = options.defaultModel;
|
|
8693
|
+
this.defaultProvider = options.defaultProvider;
|
|
8694
|
+
this.defaultTools = options.defaultTools;
|
|
8695
|
+
if ("messages" in sessionData && sessionData.messages) {
|
|
8696
|
+
this.messages = sessionData.messages.map((m) => ({
|
|
8697
|
+
type: "message",
|
|
8698
|
+
role: m.role,
|
|
8699
|
+
content: m.content,
|
|
8700
|
+
seq: m.seq,
|
|
8701
|
+
createdAt: new Date(m.created_at),
|
|
8702
|
+
runId: m.run_id ? parseRunId(m.run_id) : void 0
|
|
8703
|
+
}));
|
|
8704
|
+
this.nextSeq = this.messages.length + 1;
|
|
8705
|
+
}
|
|
8401
8706
|
}
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
retries,
|
|
8418
|
-
context: info.context
|
|
8707
|
+
/**
|
|
8708
|
+
* Create a new remote session on the server.
|
|
8709
|
+
*
|
|
8710
|
+
* @param client - ModelRelay client
|
|
8711
|
+
* @param options - Session configuration
|
|
8712
|
+
* @returns A new RemoteSession instance
|
|
8713
|
+
*/
|
|
8714
|
+
static async create(client, options = {}) {
|
|
8715
|
+
const http = getHTTPClient(client);
|
|
8716
|
+
const response = await http.request("/sessions", {
|
|
8717
|
+
method: "POST",
|
|
8718
|
+
body: {
|
|
8719
|
+
end_user_id: options.endUserId,
|
|
8720
|
+
metadata: options.metadata || {}
|
|
8721
|
+
}
|
|
8419
8722
|
});
|
|
8723
|
+
const data = await response.json();
|
|
8724
|
+
return new _RemoteSession(client, http, data, options);
|
|
8420
8725
|
}
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
// src/customer_scoped.ts
|
|
8436
|
-
function normalizeCustomerId(customerId) {
|
|
8437
|
-
const trimmed = customerId?.trim?.() ? customerId.trim() : "";
|
|
8438
|
-
if (!trimmed) {
|
|
8439
|
-
throw new ConfigError("customerId is required");
|
|
8440
|
-
}
|
|
8441
|
-
return trimmed;
|
|
8442
|
-
}
|
|
8443
|
-
function mergeCustomerOptions(customerId, options = {}) {
|
|
8444
|
-
if (options.customerId && options.customerId !== customerId) {
|
|
8445
|
-
throw new ConfigError("customerId mismatch", {
|
|
8446
|
-
expected: customerId,
|
|
8447
|
-
received: options.customerId
|
|
8726
|
+
/**
|
|
8727
|
+
* Get an existing remote session by ID.
|
|
8728
|
+
*
|
|
8729
|
+
* @param client - ModelRelay client
|
|
8730
|
+
* @param sessionId - ID of the session to retrieve
|
|
8731
|
+
* @param options - Optional configuration (toolRegistry, defaults)
|
|
8732
|
+
* @returns The RemoteSession instance
|
|
8733
|
+
*/
|
|
8734
|
+
static async get(client, sessionId, options = {}) {
|
|
8735
|
+
const http = getHTTPClient(client);
|
|
8736
|
+
const id = typeof sessionId === "string" ? sessionId : String(sessionId);
|
|
8737
|
+
const response = await http.request(`/sessions/${id}`, {
|
|
8738
|
+
method: "GET"
|
|
8448
8739
|
});
|
|
8740
|
+
const data = await response.json();
|
|
8741
|
+
return new _RemoteSession(client, http, data, options);
|
|
8449
8742
|
}
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
8743
|
+
/**
|
|
8744
|
+
* List remote sessions.
|
|
8745
|
+
*
|
|
8746
|
+
* @param client - ModelRelay client
|
|
8747
|
+
* @param options - List options
|
|
8748
|
+
* @returns Paginated list of session info
|
|
8749
|
+
*/
|
|
8750
|
+
static async list(client, options = {}) {
|
|
8751
|
+
const http = getHTTPClient(client);
|
|
8752
|
+
const params = new URLSearchParams();
|
|
8753
|
+
if (options.limit) params.set("limit", String(options.limit));
|
|
8754
|
+
if (options.offset) params.set("offset", String(options.offset));
|
|
8755
|
+
if (options.endUserId) params.set("end_user_id", options.endUserId);
|
|
8756
|
+
const response = await http.request(
|
|
8757
|
+
`/sessions${params.toString() ? `?${params.toString()}` : ""}`,
|
|
8758
|
+
{ method: "GET" }
|
|
8759
|
+
);
|
|
8760
|
+
const data = await response.json();
|
|
8761
|
+
return {
|
|
8762
|
+
sessions: data.sessions.map((s) => ({
|
|
8763
|
+
id: asSessionId(s.id),
|
|
8764
|
+
messageCount: s.message_count,
|
|
8765
|
+
metadata: s.metadata,
|
|
8766
|
+
createdAt: new Date(s.created_at),
|
|
8767
|
+
updatedAt: new Date(s.updated_at)
|
|
8768
|
+
})),
|
|
8769
|
+
nextCursor: data.next_cursor
|
|
8770
|
+
};
|
|
8456
8771
|
}
|
|
8457
|
-
|
|
8458
|
-
|
|
8772
|
+
/**
|
|
8773
|
+
* Delete a remote session.
|
|
8774
|
+
*
|
|
8775
|
+
* @param client - ModelRelay client
|
|
8776
|
+
* @param sessionId - ID of the session to delete
|
|
8777
|
+
*/
|
|
8778
|
+
static async delete(client, sessionId) {
|
|
8779
|
+
const http = getHTTPClient(client);
|
|
8780
|
+
const id = typeof sessionId === "string" ? sessionId : String(sessionId);
|
|
8781
|
+
await http.request(`/sessions/${id}`, {
|
|
8782
|
+
method: "DELETE"
|
|
8783
|
+
});
|
|
8459
8784
|
}
|
|
8460
|
-
|
|
8461
|
-
|
|
8785
|
+
// ============================================================================
|
|
8786
|
+
// Session Interface Implementation
|
|
8787
|
+
// ============================================================================
|
|
8788
|
+
/**
|
|
8789
|
+
* Full conversation history (read-only).
|
|
8790
|
+
*/
|
|
8791
|
+
get history() {
|
|
8792
|
+
return this.messages;
|
|
8462
8793
|
}
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8794
|
+
/**
|
|
8795
|
+
* Execute a prompt as a new turn in this session.
|
|
8796
|
+
*/
|
|
8797
|
+
async run(prompt, options = {}) {
|
|
8798
|
+
const userMessage = this.addMessage({
|
|
8799
|
+
type: "message",
|
|
8800
|
+
role: "user",
|
|
8801
|
+
content: [{ type: "text", text: prompt }]
|
|
8802
|
+
});
|
|
8803
|
+
this.resetRunState();
|
|
8804
|
+
try {
|
|
8805
|
+
const input = this.buildInput();
|
|
8806
|
+
const tools = mergeTools2(this.defaultTools, options.tools);
|
|
8807
|
+
const spec = {
|
|
8808
|
+
kind: "workflow.v0",
|
|
8809
|
+
name: `session-${this.id}-turn-${this.nextSeq}`,
|
|
8810
|
+
nodes: [
|
|
8811
|
+
{
|
|
8812
|
+
id: "main",
|
|
8813
|
+
type: "llm.responses",
|
|
8814
|
+
input: {
|
|
8815
|
+
request: {
|
|
8816
|
+
provider: options.provider || this.defaultProvider,
|
|
8817
|
+
model: options.model || this.defaultModel,
|
|
8818
|
+
input,
|
|
8819
|
+
tools
|
|
8820
|
+
},
|
|
8821
|
+
tool_execution: this.toolRegistry ? { mode: "client" } : void 0
|
|
8822
|
+
}
|
|
8823
|
+
}
|
|
8824
|
+
],
|
|
8825
|
+
outputs: [{ name: "result", from: "main" }]
|
|
8826
|
+
};
|
|
8827
|
+
const run = await this.client.runs.create(spec, {
|
|
8828
|
+
customerId: options.customerId || this.endUserId
|
|
8470
8829
|
});
|
|
8830
|
+
this.currentRunId = run.run_id;
|
|
8831
|
+
return await this.processRunEvents(options.signal);
|
|
8832
|
+
} catch (err) {
|
|
8833
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
8834
|
+
return {
|
|
8835
|
+
status: "error",
|
|
8836
|
+
error: error.message,
|
|
8837
|
+
runId: this.currentRunId || parseRunId("unknown"),
|
|
8838
|
+
usage: { ...this.currentUsage },
|
|
8839
|
+
events: [...this.currentEvents]
|
|
8840
|
+
};
|
|
8471
8841
|
}
|
|
8472
8842
|
}
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8843
|
+
/**
|
|
8844
|
+
* Submit tool results for a waiting run.
|
|
8845
|
+
*/
|
|
8846
|
+
async submitToolResults(results) {
|
|
8847
|
+
if (!this.currentRunId || !this.currentNodeId || !this.currentWaiting) {
|
|
8848
|
+
throw new Error("No pending tool calls to submit results for");
|
|
8849
|
+
}
|
|
8850
|
+
await this.client.runs.submitToolResults(this.currentRunId, {
|
|
8851
|
+
node_id: this.currentNodeId,
|
|
8852
|
+
step: this.currentWaiting.step,
|
|
8853
|
+
request_id: this.currentWaiting.request_id,
|
|
8854
|
+
results: results.map((r) => ({
|
|
8855
|
+
tool_call_id: r.toolCallId,
|
|
8856
|
+
name: r.toolName,
|
|
8857
|
+
output: r.error ? `Error: ${r.error}` : typeof r.result === "string" ? r.result : JSON.stringify(r.result)
|
|
8858
|
+
}))
|
|
8859
|
+
});
|
|
8860
|
+
this.currentWaiting = void 0;
|
|
8861
|
+
return await this.processRunEvents();
|
|
8487
8862
|
}
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
mergeCustomerOptions(this.customerId, options)
|
|
8494
|
-
);
|
|
8863
|
+
/**
|
|
8864
|
+
* Get all artifacts produced during this session.
|
|
8865
|
+
*/
|
|
8866
|
+
getArtifacts() {
|
|
8867
|
+
return new Map(this.artifacts);
|
|
8495
8868
|
}
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
user,
|
|
8501
|
-
mergeCustomerOptions(this.customerId, options)
|
|
8502
|
-
);
|
|
8869
|
+
/**
|
|
8870
|
+
* Close the session (no-op for remote sessions).
|
|
8871
|
+
*/
|
|
8872
|
+
async close() {
|
|
8503
8873
|
}
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8874
|
+
/**
|
|
8875
|
+
* Refresh the session state from the server.
|
|
8876
|
+
*/
|
|
8877
|
+
async refresh() {
|
|
8878
|
+
const response = await this.http.request(`/sessions/${this.id}`, {
|
|
8879
|
+
method: "GET"
|
|
8880
|
+
});
|
|
8881
|
+
const data = await response.json();
|
|
8882
|
+
this.metadata = data.metadata;
|
|
8883
|
+
this.updatedAt = new Date(data.updated_at);
|
|
8884
|
+
if (data.messages) {
|
|
8885
|
+
this.messages = data.messages.map((m) => ({
|
|
8886
|
+
type: "message",
|
|
8887
|
+
role: m.role,
|
|
8888
|
+
content: m.content,
|
|
8889
|
+
seq: m.seq,
|
|
8890
|
+
createdAt: new Date(m.created_at),
|
|
8891
|
+
runId: m.run_id ? parseRunId(m.run_id) : void 0
|
|
8892
|
+
}));
|
|
8893
|
+
this.nextSeq = this.messages.length + 1;
|
|
8894
|
+
}
|
|
8895
|
+
}
|
|
8896
|
+
// ============================================================================
|
|
8897
|
+
// Private Methods
|
|
8898
|
+
// ============================================================================
|
|
8899
|
+
addMessage(input, runId) {
|
|
8900
|
+
const message = {
|
|
8901
|
+
...input,
|
|
8902
|
+
seq: this.nextSeq++,
|
|
8903
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
8904
|
+
runId
|
|
8905
|
+
};
|
|
8906
|
+
this.messages.push(message);
|
|
8907
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
8908
|
+
return message;
|
|
8909
|
+
}
|
|
8910
|
+
buildInput() {
|
|
8911
|
+
return this.messages.map((m) => ({
|
|
8912
|
+
type: m.type,
|
|
8913
|
+
role: m.role,
|
|
8914
|
+
content: m.content,
|
|
8915
|
+
toolCalls: m.toolCalls,
|
|
8916
|
+
toolCallId: m.toolCallId
|
|
8917
|
+
}));
|
|
8511
8918
|
}
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8919
|
+
resetRunState() {
|
|
8920
|
+
this.currentRunId = void 0;
|
|
8921
|
+
this.currentNodeId = void 0;
|
|
8922
|
+
this.currentWaiting = void 0;
|
|
8923
|
+
this.currentEvents = [];
|
|
8924
|
+
this.currentUsage = {
|
|
8925
|
+
inputTokens: 0,
|
|
8926
|
+
outputTokens: 0,
|
|
8927
|
+
totalTokens: 0,
|
|
8928
|
+
llmCalls: 0,
|
|
8929
|
+
toolCalls: 0
|
|
8930
|
+
};
|
|
8518
8931
|
}
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
const
|
|
8524
|
-
|
|
8525
|
-
baseUrl: cfg.baseUrl || DEFAULT_BASE_URL,
|
|
8526
|
-
fetchImpl: cfg.fetch,
|
|
8527
|
-
clientHeader: cfg.clientHeader || DEFAULT_CLIENT_HEADER,
|
|
8528
|
-
apiKey: publishableKey
|
|
8932
|
+
async processRunEvents(signal) {
|
|
8933
|
+
if (!this.currentRunId) {
|
|
8934
|
+
throw new Error("No current run");
|
|
8935
|
+
}
|
|
8936
|
+
const eventStream = await this.client.runs.events(this.currentRunId, {
|
|
8937
|
+
afterSeq: this.currentEvents.length
|
|
8529
8938
|
});
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8939
|
+
for await (const event of eventStream) {
|
|
8940
|
+
if (signal?.aborted) {
|
|
8941
|
+
return {
|
|
8942
|
+
status: "canceled",
|
|
8943
|
+
runId: this.currentRunId,
|
|
8944
|
+
usage: { ...this.currentUsage },
|
|
8945
|
+
events: [...this.currentEvents]
|
|
8946
|
+
};
|
|
8947
|
+
}
|
|
8948
|
+
this.currentEvents.push(event);
|
|
8949
|
+
switch (event.type) {
|
|
8950
|
+
case "node_llm_call":
|
|
8951
|
+
this.currentUsage = {
|
|
8952
|
+
...this.currentUsage,
|
|
8953
|
+
llmCalls: this.currentUsage.llmCalls + 1,
|
|
8954
|
+
inputTokens: this.currentUsage.inputTokens + (event.llm_call.usage?.input_tokens || 0),
|
|
8955
|
+
outputTokens: this.currentUsage.outputTokens + (event.llm_call.usage?.output_tokens || 0),
|
|
8956
|
+
totalTokens: this.currentUsage.totalTokens + (event.llm_call.usage?.total_tokens || 0)
|
|
8957
|
+
};
|
|
8958
|
+
break;
|
|
8959
|
+
case "node_tool_call":
|
|
8960
|
+
this.currentUsage = {
|
|
8961
|
+
...this.currentUsage,
|
|
8962
|
+
toolCalls: this.currentUsage.toolCalls + 1
|
|
8963
|
+
};
|
|
8964
|
+
break;
|
|
8965
|
+
case "node_waiting":
|
|
8966
|
+
this.currentNodeId = event.node_id;
|
|
8967
|
+
this.currentWaiting = event.waiting;
|
|
8968
|
+
if (this.toolRegistry && event.waiting.reason === "tool_results" && event.waiting.pending_tool_calls && event.waiting.pending_tool_calls.length > 0) {
|
|
8969
|
+
const results = await this.executeToolsLocally(
|
|
8970
|
+
event.waiting.pending_tool_calls
|
|
8971
|
+
);
|
|
8972
|
+
if (results) {
|
|
8973
|
+
return this.submitToolResults(results);
|
|
8974
|
+
}
|
|
8975
|
+
}
|
|
8976
|
+
if (event.waiting.reason === "tool_results" && event.waiting.pending_tool_calls) {
|
|
8977
|
+
return {
|
|
8978
|
+
status: "waiting_for_tools",
|
|
8979
|
+
pendingTools: event.waiting.pending_tool_calls.map(
|
|
8980
|
+
(tc) => ({
|
|
8981
|
+
toolCallId: tc.tool_call_id,
|
|
8982
|
+
name: tc.name,
|
|
8983
|
+
arguments: tc.arguments
|
|
8984
|
+
})
|
|
8985
|
+
),
|
|
8986
|
+
runId: this.currentRunId,
|
|
8987
|
+
usage: { ...this.currentUsage },
|
|
8988
|
+
events: [...this.currentEvents]
|
|
8989
|
+
};
|
|
8990
|
+
}
|
|
8991
|
+
break;
|
|
8992
|
+
case "run_completed": {
|
|
8993
|
+
const runState2 = await this.client.runs.get(this.currentRunId);
|
|
8994
|
+
let output2;
|
|
8995
|
+
if (runState2.outputs && Array.isArray(runState2.outputs)) {
|
|
8996
|
+
output2 = runState2.outputs.filter((o) => o.type === "text").map((o) => o.text || "").join("");
|
|
8997
|
+
}
|
|
8998
|
+
if (output2) {
|
|
8999
|
+
this.addMessage(
|
|
9000
|
+
{
|
|
9001
|
+
type: "message",
|
|
9002
|
+
role: "assistant",
|
|
9003
|
+
content: [{ type: "text", text: output2 }]
|
|
9004
|
+
},
|
|
9005
|
+
this.currentRunId
|
|
9006
|
+
);
|
|
9007
|
+
}
|
|
9008
|
+
return {
|
|
9009
|
+
status: "complete",
|
|
9010
|
+
output: output2,
|
|
9011
|
+
runId: this.currentRunId,
|
|
9012
|
+
usage: { ...this.currentUsage },
|
|
9013
|
+
events: [...this.currentEvents]
|
|
9014
|
+
};
|
|
9015
|
+
}
|
|
9016
|
+
case "run_failed":
|
|
9017
|
+
return {
|
|
9018
|
+
status: "error",
|
|
9019
|
+
error: "Run failed",
|
|
9020
|
+
runId: this.currentRunId,
|
|
9021
|
+
usage: { ...this.currentUsage },
|
|
9022
|
+
events: [...this.currentEvents]
|
|
9023
|
+
};
|
|
9024
|
+
case "run_canceled":
|
|
9025
|
+
return {
|
|
9026
|
+
status: "canceled",
|
|
9027
|
+
runId: this.currentRunId,
|
|
9028
|
+
usage: { ...this.currentUsage },
|
|
9029
|
+
events: [...this.currentEvents]
|
|
9030
|
+
};
|
|
9031
|
+
}
|
|
8537
9032
|
}
|
|
8538
|
-
const
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
deviceId: this.customer.deviceId,
|
|
8543
|
-
ttlSeconds: this.customer.ttlSeconds
|
|
8544
|
-
};
|
|
8545
|
-
let token;
|
|
8546
|
-
if (this.customer.email) {
|
|
8547
|
-
const req = {
|
|
8548
|
-
...reqBase,
|
|
8549
|
-
email: this.customer.email
|
|
8550
|
-
};
|
|
8551
|
-
token = await this.auth.frontendTokenAutoProvision(req);
|
|
8552
|
-
} else {
|
|
8553
|
-
const req = reqBase;
|
|
8554
|
-
token = await this.auth.frontendToken(req);
|
|
9033
|
+
const runState = await this.client.runs.get(this.currentRunId);
|
|
9034
|
+
let output;
|
|
9035
|
+
if (runState.outputs && Array.isArray(runState.outputs)) {
|
|
9036
|
+
output = runState.outputs.filter((o) => o.type === "text").map((o) => o.text || "").join("");
|
|
8555
9037
|
}
|
|
8556
|
-
if (
|
|
8557
|
-
|
|
9038
|
+
if (output) {
|
|
9039
|
+
this.addMessage(
|
|
9040
|
+
{
|
|
9041
|
+
type: "message",
|
|
9042
|
+
role: "assistant",
|
|
9043
|
+
content: [{ type: "text", text: output }]
|
|
9044
|
+
},
|
|
9045
|
+
this.currentRunId
|
|
9046
|
+
);
|
|
8558
9047
|
}
|
|
8559
|
-
return
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
baseUrl: cfg.baseUrl || DEFAULT_BASE_URL,
|
|
8567
|
-
fetchImpl: cfg.fetch,
|
|
8568
|
-
clientHeader: cfg.clientHeader || DEFAULT_CLIENT_HEADER,
|
|
8569
|
-
apiKey: key
|
|
8570
|
-
});
|
|
8571
|
-
this.auth = new AuthClient(http, { apiKey: key });
|
|
8572
|
-
this.req = cfg.request;
|
|
9048
|
+
return {
|
|
9049
|
+
status: "complete",
|
|
9050
|
+
output,
|
|
9051
|
+
runId: this.currentRunId,
|
|
9052
|
+
usage: { ...this.currentUsage },
|
|
9053
|
+
events: [...this.currentEvents]
|
|
9054
|
+
};
|
|
8573
9055
|
}
|
|
8574
|
-
async
|
|
8575
|
-
if (this.
|
|
8576
|
-
|
|
9056
|
+
async executeToolsLocally(toolCalls) {
|
|
9057
|
+
if (!this.toolRegistry) return null;
|
|
9058
|
+
for (const tc of toolCalls) {
|
|
9059
|
+
if (!this.toolRegistry.has(tc.name)) {
|
|
9060
|
+
return null;
|
|
9061
|
+
}
|
|
8577
9062
|
}
|
|
8578
|
-
const
|
|
8579
|
-
|
|
8580
|
-
|
|
9063
|
+
const results = [];
|
|
9064
|
+
for (const tc of toolCalls) {
|
|
9065
|
+
const toolCall = {
|
|
9066
|
+
id: tc.tool_call_id,
|
|
9067
|
+
type: "function",
|
|
9068
|
+
function: {
|
|
9069
|
+
name: tc.name,
|
|
9070
|
+
arguments: tc.arguments
|
|
9071
|
+
}
|
|
9072
|
+
};
|
|
9073
|
+
const result = await this.toolRegistry.execute(toolCall);
|
|
9074
|
+
results.push(result);
|
|
9075
|
+
}
|
|
9076
|
+
return results;
|
|
8581
9077
|
}
|
|
8582
9078
|
};
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
this.request = { idToken: "", projectId: cfg.projectId };
|
|
8595
|
-
}
|
|
8596
|
-
async getToken() {
|
|
8597
|
-
if (this.cached && isReusable(this.cached)) {
|
|
8598
|
-
return this.cached.token;
|
|
9079
|
+
function getHTTPClient(client) {
|
|
9080
|
+
return client.http;
|
|
9081
|
+
}
|
|
9082
|
+
function mergeTools2(defaults, overrides) {
|
|
9083
|
+
if (!defaults && !overrides) return void 0;
|
|
9084
|
+
if (!defaults) return overrides;
|
|
9085
|
+
if (!overrides) return defaults;
|
|
9086
|
+
const merged = /* @__PURE__ */ new Map();
|
|
9087
|
+
for (const tool of defaults) {
|
|
9088
|
+
if (tool.type === "function" && tool.function) {
|
|
9089
|
+
merged.set(tool.function.name, tool);
|
|
8599
9090
|
}
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
9091
|
+
}
|
|
9092
|
+
for (const tool of overrides) {
|
|
9093
|
+
if (tool.type === "function" && tool.function) {
|
|
9094
|
+
merged.set(tool.function.name, tool);
|
|
8603
9095
|
}
|
|
8604
|
-
const token = await this.auth.oidcExchange({ ...this.request, idToken });
|
|
8605
|
-
this.cached = token;
|
|
8606
|
-
return token.token;
|
|
8607
9096
|
}
|
|
8608
|
-
|
|
9097
|
+
return Array.from(merged.values());
|
|
9098
|
+
}
|
|
8609
9099
|
|
|
8610
|
-
// src/
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
9100
|
+
// src/sessions/client.ts
|
|
9101
|
+
var SessionsClient = class {
|
|
9102
|
+
constructor(modelRelay, http, auth) {
|
|
9103
|
+
this.modelRelay = modelRelay;
|
|
9104
|
+
this.http = http;
|
|
9105
|
+
this.auth = auth;
|
|
8615
9106
|
}
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
9107
|
+
// ============================================================================
|
|
9108
|
+
// Local Sessions (Client-Managed)
|
|
9109
|
+
// ============================================================================
|
|
9110
|
+
/**
|
|
9111
|
+
* Create a new local session.
|
|
9112
|
+
*
|
|
9113
|
+
* Local sessions keep history on the client side with optional persistence.
|
|
9114
|
+
* Use for privacy-sensitive workflows or offline-capable agents.
|
|
9115
|
+
*
|
|
9116
|
+
* @param options - Session configuration
|
|
9117
|
+
* @returns A new LocalSession instance
|
|
9118
|
+
*
|
|
9119
|
+
* @example
|
|
9120
|
+
* ```typescript
|
|
9121
|
+
* const session = client.sessions.createLocal({
|
|
9122
|
+
* toolRegistry: createLocalFSTools({ root: process.cwd() }),
|
|
9123
|
+
* persistence: "memory", // or "file", "sqlite"
|
|
9124
|
+
* });
|
|
9125
|
+
*
|
|
9126
|
+
* const result = await session.run("Create a hello world file");
|
|
9127
|
+
* ```
|
|
9128
|
+
*/
|
|
9129
|
+
createLocal(options = {}) {
|
|
9130
|
+
return createLocalSession(this.modelRelay, options);
|
|
8619
9131
|
}
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
9132
|
+
/**
|
|
9133
|
+
* Resume an existing local session from storage.
|
|
9134
|
+
*
|
|
9135
|
+
* @param sessionId - ID of the session to resume
|
|
9136
|
+
* @param options - Session configuration (must match original persistence settings)
|
|
9137
|
+
* @returns The resumed LocalSession, or null if not found
|
|
9138
|
+
*
|
|
9139
|
+
* @example
|
|
9140
|
+
* ```typescript
|
|
9141
|
+
* const session = await client.sessions.resumeLocal("session-id", {
|
|
9142
|
+
* persistence: "sqlite",
|
|
9143
|
+
* });
|
|
9144
|
+
*
|
|
9145
|
+
* if (session) {
|
|
9146
|
+
* console.log(`Resumed session with ${session.history.length} messages`);
|
|
9147
|
+
* const result = await session.run("Continue where we left off");
|
|
9148
|
+
* }
|
|
9149
|
+
* ```
|
|
9150
|
+
*/
|
|
9151
|
+
async resumeLocal(sessionId, options = {}) {
|
|
9152
|
+
return LocalSession.resume(this.modelRelay, sessionId, options);
|
|
8624
9153
|
}
|
|
8625
|
-
|
|
8626
|
-
|
|
9154
|
+
// ============================================================================
|
|
9155
|
+
// Remote Sessions (Server-Managed)
|
|
9156
|
+
// ============================================================================
|
|
9157
|
+
/**
|
|
9158
|
+
* Create a new remote session.
|
|
9159
|
+
*
|
|
9160
|
+
* Remote sessions store history on the server for cross-device continuity.
|
|
9161
|
+
* Use for browser-based agents or team collaboration.
|
|
9162
|
+
*
|
|
9163
|
+
* @param options - Session configuration
|
|
9164
|
+
* @returns A new RemoteSession instance
|
|
9165
|
+
*
|
|
9166
|
+
* @example
|
|
9167
|
+
* ```typescript
|
|
9168
|
+
* const session = await client.sessions.create({
|
|
9169
|
+
* metadata: { name: "Feature implementation" },
|
|
9170
|
+
* });
|
|
9171
|
+
*
|
|
9172
|
+
* const result = await session.run("Implement the login feature");
|
|
9173
|
+
* ```
|
|
9174
|
+
*/
|
|
9175
|
+
async create(options = {}) {
|
|
9176
|
+
return RemoteSession.create(this.modelRelay, options);
|
|
8627
9177
|
}
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
9178
|
+
/**
|
|
9179
|
+
* Get an existing remote session by ID.
|
|
9180
|
+
*
|
|
9181
|
+
* @param sessionId - ID of the session to retrieve
|
|
9182
|
+
* @param options - Optional configuration (toolRegistry, defaults)
|
|
9183
|
+
* @returns The RemoteSession instance
|
|
9184
|
+
*
|
|
9185
|
+
* @example
|
|
9186
|
+
* ```typescript
|
|
9187
|
+
* const session = await client.sessions.get("session-id");
|
|
9188
|
+
* console.log(`Session has ${session.history.length} messages`);
|
|
9189
|
+
* ```
|
|
9190
|
+
*/
|
|
9191
|
+
async get(sessionId, options = {}) {
|
|
9192
|
+
return RemoteSession.get(this.modelRelay, sessionId, options);
|
|
9193
|
+
}
|
|
9194
|
+
/**
|
|
9195
|
+
* List remote sessions.
|
|
9196
|
+
*
|
|
9197
|
+
* @param options - List options (limit, cursor, endUserId)
|
|
9198
|
+
* @returns Paginated list of session summaries
|
|
9199
|
+
*
|
|
9200
|
+
* @example
|
|
9201
|
+
* ```typescript
|
|
9202
|
+
* const { sessions, nextCursor } = await client.sessions.list({ limit: 10 });
|
|
9203
|
+
* for (const info of sessions) {
|
|
9204
|
+
* console.log(`Session ${info.id}: ${info.messageCount} messages`);
|
|
9205
|
+
* }
|
|
9206
|
+
* ```
|
|
9207
|
+
*/
|
|
9208
|
+
async list(options = {}) {
|
|
9209
|
+
return RemoteSession.list(this.modelRelay, {
|
|
9210
|
+
limit: options.limit,
|
|
9211
|
+
offset: options.cursor ? parseInt(options.cursor, 10) : void 0,
|
|
9212
|
+
endUserId: options.endUserId
|
|
8642
9213
|
});
|
|
8643
9214
|
}
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
const clientId = req.clientId?.trim();
|
|
8659
|
-
if (!clientId) {
|
|
8660
|
-
throw new ConfigError("clientId is required");
|
|
9215
|
+
/**
|
|
9216
|
+
* Delete a remote session.
|
|
9217
|
+
*
|
|
9218
|
+
* Requires a secret key (not publishable key).
|
|
9219
|
+
*
|
|
9220
|
+
* @param sessionId - ID of the session to delete
|
|
9221
|
+
*
|
|
9222
|
+
* @example
|
|
9223
|
+
* ```typescript
|
|
9224
|
+
* await client.sessions.delete("session-id");
|
|
9225
|
+
* ```
|
|
9226
|
+
*/
|
|
9227
|
+
async delete(sessionId) {
|
|
9228
|
+
return RemoteSession.delete(this.modelRelay, sessionId);
|
|
8661
9229
|
}
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
9230
|
+
};
|
|
9231
|
+
|
|
9232
|
+
// src/http.ts
|
|
9233
|
+
var HTTPClient = class {
|
|
9234
|
+
constructor(cfg) {
|
|
9235
|
+
const resolvedBase = normalizeBaseUrl(cfg.baseUrl || DEFAULT_BASE_URL);
|
|
9236
|
+
if (!isValidHttpUrl(resolvedBase)) {
|
|
9237
|
+
throw new ConfigError(
|
|
9238
|
+
"baseUrl must start with http:// or https://"
|
|
9239
|
+
);
|
|
9240
|
+
}
|
|
9241
|
+
this.baseUrl = resolvedBase;
|
|
9242
|
+
this.apiKey = cfg.apiKey ? parseApiKey(cfg.apiKey) : void 0;
|
|
9243
|
+
this.accessToken = cfg.accessToken?.trim();
|
|
9244
|
+
this.fetchImpl = cfg.fetchImpl;
|
|
9245
|
+
this.clientHeader = cfg.clientHeader?.trim() || DEFAULT_CLIENT_HEADER;
|
|
9246
|
+
this.defaultConnectTimeoutMs = cfg.connectTimeoutMs === void 0 ? DEFAULT_CONNECT_TIMEOUT_MS : Math.max(0, cfg.connectTimeoutMs);
|
|
9247
|
+
this.defaultTimeoutMs = cfg.timeoutMs === void 0 ? DEFAULT_REQUEST_TIMEOUT_MS : Math.max(0, cfg.timeoutMs);
|
|
9248
|
+
this.retry = normalizeRetryConfig(cfg.retry);
|
|
9249
|
+
this.defaultHeaders = normalizeHeaders(cfg.defaultHeaders);
|
|
9250
|
+
this.metrics = cfg.metrics;
|
|
9251
|
+
this.trace = cfg.trace;
|
|
8665
9252
|
}
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
9253
|
+
async request(path2, options = {}) {
|
|
9254
|
+
const fetchFn = this.fetchImpl ?? globalThis.fetch;
|
|
9255
|
+
if (!fetchFn) {
|
|
9256
|
+
throw new ConfigError(
|
|
9257
|
+
"fetch is not available; provide a fetch implementation"
|
|
9258
|
+
);
|
|
8671
9259
|
}
|
|
8672
|
-
const
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
const
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
9260
|
+
const method = options.method || "GET";
|
|
9261
|
+
const url = buildUrl(this.baseUrl, path2);
|
|
9262
|
+
const metrics = mergeMetrics(this.metrics, options.metrics);
|
|
9263
|
+
const trace = mergeTrace(this.trace, options.trace);
|
|
9264
|
+
const context = {
|
|
9265
|
+
method,
|
|
9266
|
+
path: path2,
|
|
9267
|
+
...options.context || {}
|
|
9268
|
+
};
|
|
9269
|
+
trace?.requestStart?.(context);
|
|
9270
|
+
const start = metrics?.httpRequest || trace?.requestFinish ? Date.now() : 0;
|
|
9271
|
+
const headers = new Headers({
|
|
9272
|
+
...this.defaultHeaders,
|
|
9273
|
+
...options.headers || {}
|
|
8680
9274
|
});
|
|
8681
|
-
const
|
|
8682
|
-
if (
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
9275
|
+
const accepts = options.accept || (options.raw ? void 0 : "application/json");
|
|
9276
|
+
if (accepts && !headers.has("Accept")) {
|
|
9277
|
+
headers.set("Accept", accepts);
|
|
9278
|
+
}
|
|
9279
|
+
const body = options.body;
|
|
9280
|
+
const shouldEncodeJSON = body !== void 0 && body !== null && typeof body === "object" && !(body instanceof FormData) && !(body instanceof Blob);
|
|
9281
|
+
const payload = shouldEncodeJSON ? JSON.stringify(body) : body;
|
|
9282
|
+
if (shouldEncodeJSON && !headers.has("Content-Type")) {
|
|
9283
|
+
headers.set("Content-Type", "application/json");
|
|
9284
|
+
}
|
|
9285
|
+
const accessToken = options.accessToken ?? this.accessToken;
|
|
9286
|
+
if (accessToken) {
|
|
9287
|
+
const bearer = accessToken.toLowerCase().startsWith("bearer ") ? accessToken : `Bearer ${accessToken}`;
|
|
9288
|
+
headers.set("Authorization", bearer);
|
|
9289
|
+
}
|
|
9290
|
+
const apiKey = options.apiKey ?? this.apiKey;
|
|
9291
|
+
if (apiKey) {
|
|
9292
|
+
headers.set("X-ModelRelay-Api-Key", apiKey);
|
|
9293
|
+
}
|
|
9294
|
+
if (this.clientHeader && !headers.has("X-ModelRelay-Client")) {
|
|
9295
|
+
headers.set("X-ModelRelay-Client", this.clientHeader);
|
|
9296
|
+
}
|
|
9297
|
+
const timeoutMs = options.useDefaultTimeout === false ? options.timeoutMs : options.timeoutMs ?? this.defaultTimeoutMs;
|
|
9298
|
+
const connectTimeoutMs = options.useDefaultConnectTimeout === false ? options.connectTimeoutMs : options.connectTimeoutMs ?? this.defaultConnectTimeoutMs;
|
|
9299
|
+
const retryCfg = normalizeRetryConfig(
|
|
9300
|
+
options.retry === void 0 ? this.retry : options.retry
|
|
9301
|
+
);
|
|
9302
|
+
const attempts = retryCfg ? Math.max(1, retryCfg.maxAttempts) : 1;
|
|
9303
|
+
let lastError;
|
|
9304
|
+
let lastStatus;
|
|
9305
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
9306
|
+
let connectTimedOut = false;
|
|
9307
|
+
let requestTimedOut = false;
|
|
9308
|
+
const connectController = connectTimeoutMs && connectTimeoutMs > 0 ? new AbortController() : void 0;
|
|
9309
|
+
const requestController = timeoutMs && timeoutMs > 0 ? new AbortController() : void 0;
|
|
9310
|
+
const signal = mergeSignals(
|
|
9311
|
+
options.signal,
|
|
9312
|
+
connectController?.signal,
|
|
9313
|
+
requestController?.signal
|
|
9314
|
+
);
|
|
9315
|
+
const connectTimer = connectController && setTimeout(() => {
|
|
9316
|
+
connectTimedOut = true;
|
|
9317
|
+
connectController.abort(
|
|
9318
|
+
new DOMException("connect timeout", "AbortError")
|
|
9319
|
+
);
|
|
9320
|
+
}, connectTimeoutMs);
|
|
9321
|
+
const requestTimer = requestController && setTimeout(() => {
|
|
9322
|
+
requestTimedOut = true;
|
|
9323
|
+
requestController.abort(
|
|
9324
|
+
new DOMException("timeout", "AbortError")
|
|
9325
|
+
);
|
|
9326
|
+
}, timeoutMs);
|
|
9327
|
+
try {
|
|
9328
|
+
const response = await fetchFn(url, {
|
|
9329
|
+
method,
|
|
9330
|
+
headers,
|
|
9331
|
+
body: payload,
|
|
9332
|
+
signal
|
|
9333
|
+
});
|
|
9334
|
+
if (connectTimer) {
|
|
9335
|
+
clearTimeout(connectTimer);
|
|
9336
|
+
}
|
|
9337
|
+
if (!response.ok) {
|
|
9338
|
+
const shouldRetry = retryCfg && shouldRetryStatus(
|
|
9339
|
+
response.status,
|
|
9340
|
+
method,
|
|
9341
|
+
retryCfg.retryPost
|
|
9342
|
+
) && attempt < attempts;
|
|
9343
|
+
if (shouldRetry) {
|
|
9344
|
+
lastStatus = response.status;
|
|
9345
|
+
await backoff(attempt, retryCfg);
|
|
9346
|
+
continue;
|
|
9347
|
+
}
|
|
9348
|
+
const retries = buildRetryMetadata(attempt, response.status, lastError);
|
|
9349
|
+
const finishedCtx2 = withRequestId(context, response.headers);
|
|
9350
|
+
recordHttpMetrics(metrics, trace, start, retries, {
|
|
9351
|
+
status: response.status,
|
|
9352
|
+
context: finishedCtx2
|
|
8697
9353
|
});
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
9354
|
+
throw options.raw ? await parseErrorResponse(response, retries) : await parseErrorResponse(response, retries);
|
|
9355
|
+
}
|
|
9356
|
+
const finishedCtx = withRequestId(context, response.headers);
|
|
9357
|
+
recordHttpMetrics(metrics, trace, start, void 0, {
|
|
9358
|
+
status: response.status,
|
|
9359
|
+
context: finishedCtx
|
|
9360
|
+
});
|
|
9361
|
+
return response;
|
|
9362
|
+
} catch (err) {
|
|
9363
|
+
if (options.signal?.aborted) {
|
|
9364
|
+
throw err;
|
|
9365
|
+
}
|
|
9366
|
+
if (err instanceof ModelRelayError) {
|
|
9367
|
+
recordHttpMetrics(metrics, trace, start, void 0, {
|
|
9368
|
+
error: err,
|
|
9369
|
+
context
|
|
9370
|
+
});
|
|
9371
|
+
throw err;
|
|
9372
|
+
}
|
|
9373
|
+
const transportKind = classifyTransportErrorKind(
|
|
9374
|
+
err,
|
|
9375
|
+
connectTimedOut,
|
|
9376
|
+
requestTimedOut
|
|
9377
|
+
);
|
|
9378
|
+
const shouldRetry = retryCfg && isRetryableError(err, transportKind) && (method !== "POST" || retryCfg.retryPost) && attempt < attempts;
|
|
9379
|
+
if (!shouldRetry) {
|
|
9380
|
+
const retries = buildRetryMetadata(
|
|
9381
|
+
attempt,
|
|
9382
|
+
lastStatus,
|
|
9383
|
+
err instanceof Error ? err.message : String(err)
|
|
9384
|
+
);
|
|
9385
|
+
recordHttpMetrics(metrics, trace, start, retries, {
|
|
9386
|
+
error: err,
|
|
9387
|
+
context
|
|
8702
9388
|
});
|
|
9389
|
+
throw toTransportError(err, transportKind, retries);
|
|
9390
|
+
}
|
|
9391
|
+
lastError = err;
|
|
9392
|
+
await backoff(attempt, retryCfg);
|
|
9393
|
+
} finally {
|
|
9394
|
+
if (connectTimer) {
|
|
9395
|
+
clearTimeout(connectTimer);
|
|
9396
|
+
}
|
|
9397
|
+
if (requestTimer) {
|
|
9398
|
+
clearTimeout(requestTimer);
|
|
9399
|
+
}
|
|
8703
9400
|
}
|
|
8704
9401
|
}
|
|
8705
|
-
|
|
8706
|
-
|
|
8707
|
-
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
const
|
|
8712
|
-
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
|
|
9402
|
+
throw lastError instanceof Error ? lastError : new TransportError("request failed", {
|
|
9403
|
+
kind: "other",
|
|
9404
|
+
retries: buildRetryMetadata(attempts, lastStatus)
|
|
9405
|
+
});
|
|
9406
|
+
}
|
|
9407
|
+
async json(path2, options = {}) {
|
|
9408
|
+
const response = await this.request(path2, {
|
|
9409
|
+
...options,
|
|
9410
|
+
raw: true,
|
|
9411
|
+
accept: options.accept || "application/json"
|
|
9412
|
+
});
|
|
9413
|
+
if (!response.ok) {
|
|
9414
|
+
throw await parseErrorResponse(response);
|
|
9415
|
+
}
|
|
9416
|
+
if (response.status === 204) {
|
|
9417
|
+
return void 0;
|
|
9418
|
+
}
|
|
9419
|
+
try {
|
|
9420
|
+
return await response.json();
|
|
9421
|
+
} catch (err) {
|
|
9422
|
+
throw new APIError("failed to parse response JSON", {
|
|
9423
|
+
status: response.status,
|
|
9424
|
+
data: err
|
|
8716
9425
|
});
|
|
8717
9426
|
}
|
|
8718
|
-
return { accessToken, idToken, refreshToken, tokenType, scope, expiresAt };
|
|
8719
9427
|
}
|
|
9428
|
+
};
|
|
9429
|
+
function buildUrl(baseUrl, path2) {
|
|
9430
|
+
if (/^https?:\/\//i.test(path2)) {
|
|
9431
|
+
return path2;
|
|
9432
|
+
}
|
|
9433
|
+
if (!path2.startsWith("/")) {
|
|
9434
|
+
path2 = `/${path2}`;
|
|
9435
|
+
}
|
|
9436
|
+
return `${baseUrl}${path2}`;
|
|
8720
9437
|
}
|
|
8721
|
-
|
|
8722
|
-
const
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
scope: cfg.scope,
|
|
8726
|
-
audience: cfg.audience,
|
|
8727
|
-
fetch: cfg.fetch,
|
|
8728
|
-
signal: cfg.signal
|
|
8729
|
-
});
|
|
8730
|
-
await cfg.onUserCode(auth);
|
|
8731
|
-
const token = await pollOAuthDeviceToken({
|
|
8732
|
-
tokenEndpoint: cfg.tokenEndpoint,
|
|
8733
|
-
clientId: cfg.clientId,
|
|
8734
|
-
deviceCode: auth.deviceCode,
|
|
8735
|
-
intervalSeconds: auth.intervalSeconds,
|
|
8736
|
-
deadline: auth.expiresAt,
|
|
8737
|
-
fetch: cfg.fetch,
|
|
8738
|
-
signal: cfg.signal
|
|
8739
|
-
});
|
|
8740
|
-
if (!token.idToken) {
|
|
8741
|
-
throw new TransportError("oauth device flow did not return an id_token", {
|
|
8742
|
-
kind: "request",
|
|
8743
|
-
cause: token
|
|
8744
|
-
});
|
|
9438
|
+
function normalizeBaseUrl(value) {
|
|
9439
|
+
const trimmed = value.trim();
|
|
9440
|
+
if (trimmed.endsWith("/")) {
|
|
9441
|
+
return trimmed.slice(0, -1);
|
|
8745
9442
|
}
|
|
8746
|
-
return
|
|
9443
|
+
return trimmed;
|
|
8747
9444
|
}
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
if (!fetchFn) {
|
|
8751
|
-
throw new ConfigError("fetch is not available; provide a fetch implementation");
|
|
8752
|
-
}
|
|
8753
|
-
let resp;
|
|
8754
|
-
try {
|
|
8755
|
-
resp = await fetchFn(url, {
|
|
8756
|
-
method: "POST",
|
|
8757
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
8758
|
-
body: form.toString(),
|
|
8759
|
-
signal: opts.signal
|
|
8760
|
-
});
|
|
8761
|
-
} catch (cause) {
|
|
8762
|
-
throw new TransportError("oauth request failed", { kind: "request", cause });
|
|
8763
|
-
}
|
|
8764
|
-
let json;
|
|
8765
|
-
try {
|
|
8766
|
-
json = await resp.json();
|
|
8767
|
-
} catch (cause) {
|
|
8768
|
-
throw new TransportError("oauth response was not valid JSON", { kind: "request", cause });
|
|
8769
|
-
}
|
|
8770
|
-
if (!resp.ok && !opts.allowErrorPayload) {
|
|
8771
|
-
throw new TransportError(`oauth request failed (${resp.status})`, {
|
|
8772
|
-
kind: "request",
|
|
8773
|
-
cause: json
|
|
8774
|
-
});
|
|
8775
|
-
}
|
|
8776
|
-
return json || {};
|
|
9445
|
+
function isValidHttpUrl(value) {
|
|
9446
|
+
return /^https?:\/\//i.test(value);
|
|
8777
9447
|
}
|
|
8778
|
-
|
|
8779
|
-
if (
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
8783
|
-
|
|
8784
|
-
|
|
9448
|
+
function normalizeRetryConfig(retry) {
|
|
9449
|
+
if (retry === false) return void 0;
|
|
9450
|
+
const cfg = retry || {};
|
|
9451
|
+
return {
|
|
9452
|
+
maxAttempts: Math.max(1, cfg.maxAttempts ?? 3),
|
|
9453
|
+
baseBackoffMs: Math.max(0, cfg.baseBackoffMs ?? 300),
|
|
9454
|
+
maxBackoffMs: Math.max(0, cfg.maxBackoffMs ?? 5e3),
|
|
9455
|
+
retryPost: cfg.retryPost ?? true
|
|
9456
|
+
};
|
|
9457
|
+
}
|
|
9458
|
+
function shouldRetryStatus(status, method, retryPost) {
|
|
9459
|
+
if (status === 408 || status === 429) {
|
|
9460
|
+
return method !== "POST" || retryPost;
|
|
8785
9461
|
}
|
|
8786
|
-
if (
|
|
8787
|
-
|
|
9462
|
+
if (status >= 500 && status < 600) {
|
|
9463
|
+
return method !== "POST" || retryPost;
|
|
8788
9464
|
}
|
|
8789
|
-
|
|
8790
|
-
const onAbort = () => {
|
|
8791
|
-
signal.removeEventListener("abort", onAbort);
|
|
8792
|
-
reject(new TransportError("oauth device flow aborted", { kind: "request" }));
|
|
8793
|
-
};
|
|
8794
|
-
signal.addEventListener("abort", onAbort);
|
|
8795
|
-
setTimeout(() => {
|
|
8796
|
-
signal.removeEventListener("abort", onAbort);
|
|
8797
|
-
resolve();
|
|
8798
|
-
}, ms);
|
|
8799
|
-
});
|
|
8800
|
-
}
|
|
8801
|
-
|
|
8802
|
-
// src/workflow_builder.ts
|
|
8803
|
-
function transformJSONValue(from, pointer) {
|
|
8804
|
-
return pointer ? { from, pointer } : { from };
|
|
9465
|
+
return false;
|
|
8805
9466
|
}
|
|
8806
|
-
function
|
|
8807
|
-
|
|
9467
|
+
function isRetryableError(err, kind) {
|
|
9468
|
+
if (!err) return false;
|
|
9469
|
+
if (kind === "timeout" || kind === "connect") return true;
|
|
9470
|
+
return err instanceof DOMException || err instanceof TypeError;
|
|
8808
9471
|
}
|
|
8809
|
-
function
|
|
8810
|
-
|
|
9472
|
+
function backoff(attempt, cfg) {
|
|
9473
|
+
const exp = Math.max(0, attempt - 1);
|
|
9474
|
+
const base = cfg.baseBackoffMs * Math.pow(2, Math.min(exp, 10));
|
|
9475
|
+
const capped = Math.min(base, cfg.maxBackoffMs);
|
|
9476
|
+
const jitter = 0.5 + Math.random();
|
|
9477
|
+
const delay = Math.min(cfg.maxBackoffMs, capped * jitter);
|
|
9478
|
+
if (delay <= 0) return Promise.resolve();
|
|
9479
|
+
return new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
8811
9480
|
}
|
|
8812
|
-
function
|
|
8813
|
-
const
|
|
8814
|
-
if (
|
|
8815
|
-
|
|
8816
|
-
|
|
8817
|
-
|
|
8818
|
-
if (
|
|
8819
|
-
|
|
9481
|
+
function mergeSignals(...signals) {
|
|
9482
|
+
const active = signals.filter(Boolean);
|
|
9483
|
+
if (active.length === 0) return void 0;
|
|
9484
|
+
if (active.length === 1) return active[0];
|
|
9485
|
+
const controller = new AbortController();
|
|
9486
|
+
for (const src of active) {
|
|
9487
|
+
if (src.aborted) {
|
|
9488
|
+
controller.abort(src.reason);
|
|
9489
|
+
break;
|
|
8820
9490
|
}
|
|
9491
|
+
src.addEventListener(
|
|
9492
|
+
"abort",
|
|
9493
|
+
() => controller.abort(src.reason),
|
|
9494
|
+
{ once: true }
|
|
9495
|
+
);
|
|
8821
9496
|
}
|
|
8822
|
-
return
|
|
9497
|
+
return controller.signal;
|
|
8823
9498
|
}
|
|
8824
|
-
|
|
8825
|
-
|
|
8826
|
-
|
|
9499
|
+
function normalizeHeaders(headers) {
|
|
9500
|
+
if (!headers) return {};
|
|
9501
|
+
const normalized = {};
|
|
9502
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
9503
|
+
if (!key || !value) continue;
|
|
9504
|
+
const k = key.trim();
|
|
9505
|
+
const v = value.trim();
|
|
9506
|
+
if (k && v) {
|
|
9507
|
+
normalized[k] = v;
|
|
9508
|
+
}
|
|
8827
9509
|
}
|
|
8828
|
-
|
|
8829
|
-
|
|
9510
|
+
return normalized;
|
|
9511
|
+
}
|
|
9512
|
+
function buildRetryMetadata(attempt, lastStatus, lastError) {
|
|
9513
|
+
if (!attempt || attempt <= 1) return void 0;
|
|
9514
|
+
return {
|
|
9515
|
+
attempts: attempt,
|
|
9516
|
+
lastStatus,
|
|
9517
|
+
lastError: typeof lastError === "string" ? lastError : lastError instanceof Error ? lastError.message : lastError ? String(lastError) : void 0
|
|
9518
|
+
};
|
|
9519
|
+
}
|
|
9520
|
+
function classifyTransportErrorKind(err, connectTimedOut, requestTimedOut) {
|
|
9521
|
+
if (connectTimedOut) return "connect";
|
|
9522
|
+
if (requestTimedOut) return "timeout";
|
|
9523
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
9524
|
+
return requestTimedOut ? "timeout" : "request";
|
|
8830
9525
|
}
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
|
|
9526
|
+
if (err instanceof TypeError) return "request";
|
|
9527
|
+
return "other";
|
|
9528
|
+
}
|
|
9529
|
+
function toTransportError(err, kind, retries) {
|
|
9530
|
+
const message = err instanceof Error ? err.message : typeof err === "string" ? err : "request failed";
|
|
9531
|
+
return new TransportError(message, { kind, retries, cause: err });
|
|
9532
|
+
}
|
|
9533
|
+
function recordHttpMetrics(metrics, trace, start, retries, info) {
|
|
9534
|
+
if (!metrics?.httpRequest && !trace?.requestFinish) return;
|
|
9535
|
+
const latencyMs = start ? Date.now() - start : 0;
|
|
9536
|
+
if (metrics?.httpRequest) {
|
|
9537
|
+
metrics.httpRequest({
|
|
9538
|
+
latencyMs,
|
|
9539
|
+
status: info.status,
|
|
9540
|
+
error: info.error ? String(info.error) : void 0,
|
|
9541
|
+
retries,
|
|
9542
|
+
context: info.context
|
|
8835
9543
|
});
|
|
8836
9544
|
}
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
9545
|
+
trace?.requestFinish?.({
|
|
9546
|
+
context: info.context,
|
|
9547
|
+
status: info.status,
|
|
9548
|
+
error: info.error,
|
|
9549
|
+
retries,
|
|
9550
|
+
latencyMs
|
|
9551
|
+
});
|
|
9552
|
+
}
|
|
9553
|
+
function withRequestId(context, headers) {
|
|
9554
|
+
const requestId = headers.get("X-ModelRelay-Request-Id") || headers.get("X-Request-Id") || context.requestId;
|
|
9555
|
+
if (!requestId) return context;
|
|
9556
|
+
return { ...context, requestId };
|
|
9557
|
+
}
|
|
9558
|
+
|
|
9559
|
+
// src/customer_scoped.ts
|
|
9560
|
+
function normalizeCustomerId(customerId) {
|
|
9561
|
+
const trimmed = customerId?.trim?.() ? customerId.trim() : "";
|
|
9562
|
+
if (!trimmed) {
|
|
9563
|
+
throw new ConfigError("customerId is required");
|
|
8845
9564
|
}
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
};
|
|
8854
|
-
return this.node({
|
|
8855
|
-
id,
|
|
8856
|
-
type: WorkflowNodeTypes.LLMResponses,
|
|
8857
|
-
input
|
|
9565
|
+
return trimmed;
|
|
9566
|
+
}
|
|
9567
|
+
function mergeCustomerOptions(customerId, options = {}) {
|
|
9568
|
+
if (options.customerId && options.customerId !== customerId) {
|
|
9569
|
+
throw new ConfigError("customerId mismatch", {
|
|
9570
|
+
expected: customerId,
|
|
9571
|
+
received: options.customerId
|
|
8858
9572
|
});
|
|
8859
9573
|
}
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
9574
|
+
return { ...options, customerId };
|
|
9575
|
+
}
|
|
9576
|
+
var CustomerResponsesClient = class {
|
|
9577
|
+
constructor(base, customerId) {
|
|
9578
|
+
this.customerId = normalizeCustomerId(customerId);
|
|
9579
|
+
this.base = base;
|
|
8865
9580
|
}
|
|
8866
|
-
|
|
8867
|
-
return this.
|
|
9581
|
+
get id() {
|
|
9582
|
+
return this.customerId;
|
|
8868
9583
|
}
|
|
8869
|
-
|
|
8870
|
-
return this.
|
|
8871
|
-
outputs: [
|
|
8872
|
-
...this.state.outputs,
|
|
8873
|
-
{ name, from, ...pointer ? { pointer } : {} }
|
|
8874
|
-
]
|
|
8875
|
-
});
|
|
9584
|
+
new() {
|
|
9585
|
+
return this.base.new().customerId(this.customerId);
|
|
8876
9586
|
}
|
|
8877
|
-
|
|
8878
|
-
const
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
|
|
8883
|
-
|
|
8884
|
-
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
|
|
8889
|
-
|
|
8890
|
-
|
|
8891
|
-
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
|
|
8896
|
-
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
9587
|
+
ensureRequestCustomer(request) {
|
|
9588
|
+
const req = asInternal(request);
|
|
9589
|
+
const reqCustomer = req.options.customerId;
|
|
9590
|
+
if (reqCustomer && reqCustomer !== this.customerId) {
|
|
9591
|
+
throw new ConfigError("customerId mismatch", {
|
|
9592
|
+
expected: this.customerId,
|
|
9593
|
+
received: reqCustomer
|
|
9594
|
+
});
|
|
9595
|
+
}
|
|
9596
|
+
}
|
|
9597
|
+
async create(request, options = {}) {
|
|
9598
|
+
this.ensureRequestCustomer(request);
|
|
9599
|
+
return this.base.create(request, mergeCustomerOptions(this.customerId, options));
|
|
9600
|
+
}
|
|
9601
|
+
async stream(request, options = {}) {
|
|
9602
|
+
this.ensureRequestCustomer(request);
|
|
9603
|
+
return this.base.stream(request, mergeCustomerOptions(this.customerId, options));
|
|
9604
|
+
}
|
|
9605
|
+
async streamJSON(request, options = {}) {
|
|
9606
|
+
this.ensureRequestCustomer(request);
|
|
9607
|
+
return this.base.streamJSON(
|
|
9608
|
+
request,
|
|
9609
|
+
mergeCustomerOptions(this.customerId, options)
|
|
9610
|
+
);
|
|
9611
|
+
}
|
|
9612
|
+
async text(system, user, options = {}) {
|
|
9613
|
+
return this.base.textForCustomer(
|
|
9614
|
+
this.customerId,
|
|
9615
|
+
system,
|
|
9616
|
+
user,
|
|
9617
|
+
mergeCustomerOptions(this.customerId, options)
|
|
9618
|
+
);
|
|
9619
|
+
}
|
|
9620
|
+
async streamTextDeltas(system, user, options = {}) {
|
|
9621
|
+
return this.base.streamTextDeltasForCustomer(
|
|
9622
|
+
this.customerId,
|
|
9623
|
+
system,
|
|
9624
|
+
user,
|
|
9625
|
+
mergeCustomerOptions(this.customerId, options)
|
|
9626
|
+
);
|
|
9627
|
+
}
|
|
9628
|
+
};
|
|
9629
|
+
var CustomerScopedModelRelay = class {
|
|
9630
|
+
constructor(responses, customerId, baseUrl) {
|
|
9631
|
+
const normalized = normalizeCustomerId(customerId);
|
|
9632
|
+
this.responses = new CustomerResponsesClient(responses, normalized);
|
|
9633
|
+
this.customerId = normalized;
|
|
9634
|
+
this.baseUrl = baseUrl;
|
|
9635
|
+
}
|
|
9636
|
+
};
|
|
9637
|
+
|
|
9638
|
+
// src/token_providers.ts
|
|
9639
|
+
function isReusable(token) {
|
|
9640
|
+
if (!token.token) {
|
|
9641
|
+
return false;
|
|
9642
|
+
}
|
|
9643
|
+
return token.expiresAt.getTime() - Date.now() > 6e4;
|
|
9644
|
+
}
|
|
9645
|
+
var FrontendTokenProvider = class {
|
|
9646
|
+
constructor(cfg) {
|
|
9647
|
+
const publishableKey = parsePublishableKey(cfg.publishableKey);
|
|
9648
|
+
const http = new HTTPClient({
|
|
9649
|
+
baseUrl: cfg.baseUrl || DEFAULT_BASE_URL,
|
|
9650
|
+
fetchImpl: cfg.fetch,
|
|
9651
|
+
clientHeader: cfg.clientHeader || DEFAULT_CLIENT_HEADER,
|
|
9652
|
+
apiKey: publishableKey
|
|
8903
9653
|
});
|
|
8904
|
-
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
|
|
8910
|
-
|
|
9654
|
+
this.publishableKey = publishableKey;
|
|
9655
|
+
this.customer = cfg.customer;
|
|
9656
|
+
this.auth = new AuthClient(http, { apiKey: publishableKey, customer: cfg.customer });
|
|
9657
|
+
}
|
|
9658
|
+
async getToken() {
|
|
9659
|
+
if (!this.customer?.provider || !this.customer?.subject) {
|
|
9660
|
+
throw new ConfigError("customer.provider and customer.subject are required");
|
|
9661
|
+
}
|
|
9662
|
+
const reqBase = {
|
|
9663
|
+
publishableKey: this.publishableKey,
|
|
9664
|
+
identityProvider: this.customer.provider,
|
|
9665
|
+
identitySubject: this.customer.subject,
|
|
9666
|
+
deviceId: this.customer.deviceId,
|
|
9667
|
+
ttlSeconds: this.customer.ttlSeconds
|
|
8911
9668
|
};
|
|
9669
|
+
let token;
|
|
9670
|
+
if (this.customer.email) {
|
|
9671
|
+
const req = {
|
|
9672
|
+
...reqBase,
|
|
9673
|
+
email: this.customer.email
|
|
9674
|
+
};
|
|
9675
|
+
token = await this.auth.frontendTokenAutoProvision(req);
|
|
9676
|
+
} else {
|
|
9677
|
+
const req = reqBase;
|
|
9678
|
+
token = await this.auth.frontendToken(req);
|
|
9679
|
+
}
|
|
9680
|
+
if (!token.token) {
|
|
9681
|
+
throw new ConfigError("frontend token exchange returned an empty token");
|
|
9682
|
+
}
|
|
9683
|
+
return token.token;
|
|
9684
|
+
}
|
|
9685
|
+
};
|
|
9686
|
+
var CustomerTokenProvider = class {
|
|
9687
|
+
constructor(cfg) {
|
|
9688
|
+
const key = parseSecretKey(cfg.secretKey);
|
|
9689
|
+
const http = new HTTPClient({
|
|
9690
|
+
baseUrl: cfg.baseUrl || DEFAULT_BASE_URL,
|
|
9691
|
+
fetchImpl: cfg.fetch,
|
|
9692
|
+
clientHeader: cfg.clientHeader || DEFAULT_CLIENT_HEADER,
|
|
9693
|
+
apiKey: key
|
|
9694
|
+
});
|
|
9695
|
+
this.auth = new AuthClient(http, { apiKey: key });
|
|
9696
|
+
this.req = cfg.request;
|
|
9697
|
+
}
|
|
9698
|
+
async getToken() {
|
|
9699
|
+
if (this.cached && isReusable(this.cached)) {
|
|
9700
|
+
return this.cached.token;
|
|
9701
|
+
}
|
|
9702
|
+
const token = await this.auth.customerToken(this.req);
|
|
9703
|
+
this.cached = token;
|
|
9704
|
+
return token.token;
|
|
9705
|
+
}
|
|
9706
|
+
};
|
|
9707
|
+
var OIDCExchangeTokenProvider = class {
|
|
9708
|
+
constructor(cfg) {
|
|
9709
|
+
const apiKey = parseApiKey(cfg.apiKey);
|
|
9710
|
+
const http = new HTTPClient({
|
|
9711
|
+
baseUrl: cfg.baseUrl || DEFAULT_BASE_URL,
|
|
9712
|
+
fetchImpl: cfg.fetch,
|
|
9713
|
+
clientHeader: cfg.clientHeader || DEFAULT_CLIENT_HEADER,
|
|
9714
|
+
apiKey
|
|
9715
|
+
});
|
|
9716
|
+
this.auth = new AuthClient(http, { apiKey });
|
|
9717
|
+
this.idTokenProvider = cfg.idTokenProvider;
|
|
9718
|
+
this.request = { idToken: "", projectId: cfg.projectId };
|
|
9719
|
+
}
|
|
9720
|
+
async getToken() {
|
|
9721
|
+
if (this.cached && isReusable(this.cached)) {
|
|
9722
|
+
return this.cached.token;
|
|
9723
|
+
}
|
|
9724
|
+
const idToken = (await this.idTokenProvider())?.trim();
|
|
9725
|
+
if (!idToken) {
|
|
9726
|
+
throw new ConfigError("idTokenProvider returned an empty id_token");
|
|
9727
|
+
}
|
|
9728
|
+
const token = await this.auth.oidcExchange({ ...this.request, idToken });
|
|
9729
|
+
this.cached = token;
|
|
9730
|
+
return token.token;
|
|
8912
9731
|
}
|
|
8913
9732
|
};
|
|
8914
|
-
function workflowV0() {
|
|
8915
|
-
return WorkflowBuilderV0.new();
|
|
8916
|
-
}
|
|
8917
9733
|
|
|
8918
|
-
// src/
|
|
8919
|
-
|
|
8920
|
-
|
|
8921
|
-
|
|
8922
|
-
|
|
8923
|
-
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
|
|
8937
|
-
|
|
8938
|
-
|
|
8939
|
-
|
|
8940
|
-
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
8957
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
|
|
8969
|
-
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
9734
|
+
// src/device_flow.ts
|
|
9735
|
+
async function startOAuthDeviceAuthorization(req) {
|
|
9736
|
+
const deviceAuthorizationEndpoint = req.deviceAuthorizationEndpoint?.trim();
|
|
9737
|
+
if (!deviceAuthorizationEndpoint) {
|
|
9738
|
+
throw new ConfigError("deviceAuthorizationEndpoint is required");
|
|
9739
|
+
}
|
|
9740
|
+
const clientId = req.clientId?.trim();
|
|
9741
|
+
if (!clientId) {
|
|
9742
|
+
throw new ConfigError("clientId is required");
|
|
9743
|
+
}
|
|
9744
|
+
const form = new URLSearchParams();
|
|
9745
|
+
form.set("client_id", clientId);
|
|
9746
|
+
if (req.scope?.trim()) {
|
|
9747
|
+
form.set("scope", req.scope.trim());
|
|
9748
|
+
}
|
|
9749
|
+
if (req.audience?.trim()) {
|
|
9750
|
+
form.set("audience", req.audience.trim());
|
|
9751
|
+
}
|
|
9752
|
+
const payload = await postOAuthForm(deviceAuthorizationEndpoint, form, {
|
|
9753
|
+
fetch: req.fetch,
|
|
9754
|
+
signal: req.signal
|
|
9755
|
+
});
|
|
9756
|
+
const deviceCode = String(payload.device_code || "").trim();
|
|
9757
|
+
const userCode = String(payload.user_code || "").trim();
|
|
9758
|
+
const verificationUri = String(payload.verification_uri || payload.verification_uri_complete || "").trim();
|
|
9759
|
+
const verificationUriComplete = String(payload.verification_uri_complete || "").trim() || void 0;
|
|
9760
|
+
const expiresIn = Number(payload.expires_in || 0);
|
|
9761
|
+
const intervalSeconds = Math.max(1, Number(payload.interval || 5));
|
|
9762
|
+
if (!deviceCode || !userCode || !verificationUri || !expiresIn) {
|
|
9763
|
+
throw new TransportError("oauth device authorization returned an invalid response", {
|
|
9764
|
+
kind: "request",
|
|
9765
|
+
cause: payload
|
|
9766
|
+
});
|
|
9767
|
+
}
|
|
9768
|
+
return {
|
|
9769
|
+
deviceCode,
|
|
9770
|
+
userCode,
|
|
9771
|
+
verificationUri,
|
|
9772
|
+
verificationUriComplete,
|
|
9773
|
+
expiresAt: new Date(Date.now() + expiresIn * 1e3),
|
|
9774
|
+
intervalSeconds
|
|
9775
|
+
};
|
|
9776
|
+
}
|
|
9777
|
+
async function pollOAuthDeviceToken(req) {
|
|
9778
|
+
const tokenEndpoint = req.tokenEndpoint?.trim();
|
|
9779
|
+
if (!tokenEndpoint) {
|
|
9780
|
+
throw new ConfigError("tokenEndpoint is required");
|
|
9781
|
+
}
|
|
9782
|
+
const clientId = req.clientId?.trim();
|
|
9783
|
+
if (!clientId) {
|
|
9784
|
+
throw new ConfigError("clientId is required");
|
|
9785
|
+
}
|
|
9786
|
+
const deviceCode = req.deviceCode?.trim();
|
|
9787
|
+
if (!deviceCode) {
|
|
9788
|
+
throw new ConfigError("deviceCode is required");
|
|
9789
|
+
}
|
|
9790
|
+
const deadline = req.deadline ?? new Date(Date.now() + 10 * 60 * 1e3);
|
|
9791
|
+
let intervalMs = Math.max(1, req.intervalSeconds ?? 5) * 1e3;
|
|
9792
|
+
while (true) {
|
|
9793
|
+
if (Date.now() >= deadline.getTime()) {
|
|
9794
|
+
throw new TransportError("oauth device flow timed out", { kind: "timeout" });
|
|
9795
|
+
}
|
|
9796
|
+
const form = new URLSearchParams();
|
|
9797
|
+
form.set("grant_type", "urn:ietf:params:oauth:grant-type:device_code");
|
|
9798
|
+
form.set("device_code", deviceCode);
|
|
9799
|
+
form.set("client_id", clientId);
|
|
9800
|
+
const payload = await postOAuthForm(tokenEndpoint, form, {
|
|
9801
|
+
fetch: req.fetch,
|
|
9802
|
+
signal: req.signal,
|
|
9803
|
+
allowErrorPayload: true
|
|
9804
|
+
});
|
|
9805
|
+
const err = String(payload.error || "").trim();
|
|
9806
|
+
if (err) {
|
|
9807
|
+
switch (err) {
|
|
9808
|
+
case "authorization_pending":
|
|
9809
|
+
await sleep(intervalMs, req.signal);
|
|
9810
|
+
continue;
|
|
9811
|
+
case "slow_down":
|
|
9812
|
+
intervalMs += 5e3;
|
|
9813
|
+
await sleep(intervalMs, req.signal);
|
|
9814
|
+
continue;
|
|
9815
|
+
case "expired_token":
|
|
9816
|
+
case "access_denied":
|
|
9817
|
+
case "invalid_grant":
|
|
9818
|
+
throw new TransportError(`oauth device flow failed: ${err}`, {
|
|
9819
|
+
kind: "request",
|
|
9820
|
+
cause: payload
|
|
9821
|
+
});
|
|
9822
|
+
default:
|
|
9823
|
+
throw new TransportError(`oauth device flow error: ${err}`, {
|
|
9824
|
+
kind: "request",
|
|
9825
|
+
cause: payload
|
|
9826
|
+
});
|
|
9827
|
+
}
|
|
9828
|
+
}
|
|
9829
|
+
const accessToken = String(payload.access_token || "").trim() || void 0;
|
|
9830
|
+
const idToken = String(payload.id_token || "").trim() || void 0;
|
|
9831
|
+
const refreshToken = String(payload.refresh_token || "").trim() || void 0;
|
|
9832
|
+
const tokenType = String(payload.token_type || "").trim() || void 0;
|
|
9833
|
+
const scope = String(payload.scope || "").trim() || void 0;
|
|
9834
|
+
const expiresIn = payload.expires_in !== void 0 ? Number(payload.expires_in) : void 0;
|
|
9835
|
+
const expiresAt = typeof expiresIn === "number" && Number.isFinite(expiresIn) && expiresIn > 0 ? new Date(Date.now() + expiresIn * 1e3) : void 0;
|
|
9836
|
+
if (!accessToken && !idToken) {
|
|
9837
|
+
throw new TransportError("oauth device flow returned an invalid token response", {
|
|
9838
|
+
kind: "request",
|
|
9839
|
+
cause: payload
|
|
9840
|
+
});
|
|
9841
|
+
}
|
|
9842
|
+
return { accessToken, idToken, refreshToken, tokenType, scope, expiresAt };
|
|
9843
|
+
}
|
|
9844
|
+
}
|
|
9845
|
+
async function runOAuthDeviceFlowForIDToken(cfg) {
|
|
9846
|
+
const auth = await startOAuthDeviceAuthorization({
|
|
9847
|
+
deviceAuthorizationEndpoint: cfg.deviceAuthorizationEndpoint,
|
|
9848
|
+
clientId: cfg.clientId,
|
|
9849
|
+
scope: cfg.scope,
|
|
9850
|
+
audience: cfg.audience,
|
|
9851
|
+
fetch: cfg.fetch,
|
|
9852
|
+
signal: cfg.signal
|
|
9853
|
+
});
|
|
9854
|
+
await cfg.onUserCode(auth);
|
|
9855
|
+
const token = await pollOAuthDeviceToken({
|
|
9856
|
+
tokenEndpoint: cfg.tokenEndpoint,
|
|
9857
|
+
clientId: cfg.clientId,
|
|
9858
|
+
deviceCode: auth.deviceCode,
|
|
9859
|
+
intervalSeconds: auth.intervalSeconds,
|
|
9860
|
+
deadline: auth.expiresAt,
|
|
9861
|
+
fetch: cfg.fetch,
|
|
9862
|
+
signal: cfg.signal
|
|
9863
|
+
});
|
|
9864
|
+
if (!token.idToken) {
|
|
9865
|
+
throw new TransportError("oauth device flow did not return an id_token", {
|
|
9866
|
+
kind: "request",
|
|
9867
|
+
cause: token
|
|
9868
|
+
});
|
|
9869
|
+
}
|
|
9870
|
+
return token.idToken;
|
|
9871
|
+
}
|
|
9872
|
+
async function postOAuthForm(url, form, opts) {
|
|
9873
|
+
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
9874
|
+
if (!fetchFn) {
|
|
9875
|
+
throw new ConfigError("fetch is not available; provide a fetch implementation");
|
|
9876
|
+
}
|
|
9877
|
+
let resp;
|
|
9878
|
+
try {
|
|
9879
|
+
resp = await fetchFn(url, {
|
|
9880
|
+
method: "POST",
|
|
9881
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
9882
|
+
body: form.toString(),
|
|
9883
|
+
signal: opts.signal
|
|
9884
|
+
});
|
|
9885
|
+
} catch (cause) {
|
|
9886
|
+
throw new TransportError("oauth request failed", { kind: "request", cause });
|
|
9887
|
+
}
|
|
9888
|
+
let json;
|
|
9889
|
+
try {
|
|
9890
|
+
json = await resp.json();
|
|
9891
|
+
} catch (cause) {
|
|
9892
|
+
throw new TransportError("oauth response was not valid JSON", { kind: "request", cause });
|
|
9893
|
+
}
|
|
9894
|
+
if (!resp.ok && !opts.allowErrorPayload) {
|
|
9895
|
+
throw new TransportError(`oauth request failed (${resp.status})`, {
|
|
9896
|
+
kind: "request",
|
|
9897
|
+
cause: json
|
|
9898
|
+
});
|
|
9899
|
+
}
|
|
9900
|
+
return json || {};
|
|
9901
|
+
}
|
|
9902
|
+
async function sleep(ms, signal) {
|
|
9903
|
+
if (!ms || ms <= 0) {
|
|
9904
|
+
return;
|
|
9905
|
+
}
|
|
9906
|
+
if (!signal) {
|
|
9907
|
+
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
9908
|
+
return;
|
|
9909
|
+
}
|
|
9910
|
+
if (signal.aborted) {
|
|
9911
|
+
throw new TransportError("oauth device flow aborted", { kind: "request" });
|
|
9912
|
+
}
|
|
9913
|
+
await new Promise((resolve2, reject) => {
|
|
9914
|
+
const onAbort = () => {
|
|
9915
|
+
signal.removeEventListener("abort", onAbort);
|
|
9916
|
+
reject(new TransportError("oauth device flow aborted", { kind: "request" }));
|
|
9917
|
+
};
|
|
9918
|
+
signal.addEventListener("abort", onAbort);
|
|
9919
|
+
setTimeout(() => {
|
|
9920
|
+
signal.removeEventListener("abort", onAbort);
|
|
9921
|
+
resolve2();
|
|
9922
|
+
}, ms);
|
|
9923
|
+
});
|
|
9924
|
+
}
|
|
9925
|
+
|
|
9926
|
+
// src/workflow_builder.ts
|
|
9927
|
+
function transformJSONValue(from, pointer) {
|
|
9928
|
+
return pointer ? { from, pointer } : { from };
|
|
9929
|
+
}
|
|
9930
|
+
function transformJSONObject(object) {
|
|
9931
|
+
return { object };
|
|
9932
|
+
}
|
|
9933
|
+
function transformJSONMerge(merge) {
|
|
9934
|
+
return { merge: merge.slice() };
|
|
9935
|
+
}
|
|
9936
|
+
function wireRequest(req) {
|
|
9937
|
+
const raw = req;
|
|
9938
|
+
if (raw && typeof raw === "object") {
|
|
9939
|
+
if ("input" in raw) {
|
|
9940
|
+
return req;
|
|
9941
|
+
}
|
|
9942
|
+
if ("body" in raw) {
|
|
9943
|
+
return raw.body ?? {};
|
|
9944
|
+
}
|
|
9945
|
+
}
|
|
9946
|
+
return asInternal(req).body;
|
|
9947
|
+
}
|
|
9948
|
+
var WorkflowBuilderV0 = class _WorkflowBuilderV0 {
|
|
9949
|
+
constructor(state = { nodes: [], edges: [], outputs: [] }) {
|
|
9950
|
+
this.state = state;
|
|
9951
|
+
}
|
|
9952
|
+
static new() {
|
|
9953
|
+
return new _WorkflowBuilderV0();
|
|
9954
|
+
}
|
|
9955
|
+
with(patch) {
|
|
9956
|
+
return new _WorkflowBuilderV0({
|
|
9957
|
+
...this.state,
|
|
9958
|
+
...patch
|
|
9959
|
+
});
|
|
9960
|
+
}
|
|
9961
|
+
name(name) {
|
|
9962
|
+
return this.with({ name: name.trim() || void 0 });
|
|
9963
|
+
}
|
|
9964
|
+
execution(execution) {
|
|
9965
|
+
return this.with({ execution });
|
|
9966
|
+
}
|
|
9967
|
+
node(node) {
|
|
9968
|
+
return this.with({ nodes: [...this.state.nodes, node] });
|
|
9969
|
+
}
|
|
9970
|
+
llmResponses(id, request, options = {}) {
|
|
9971
|
+
const input = {
|
|
9972
|
+
request: wireRequest(request),
|
|
9973
|
+
...options.stream === void 0 ? {} : { stream: options.stream },
|
|
9974
|
+
...options.toolExecution === void 0 ? {} : { tool_execution: { mode: options.toolExecution } },
|
|
9975
|
+
...options.toolLimits === void 0 ? {} : { tool_limits: { ...options.toolLimits } },
|
|
9976
|
+
...options.bindings === void 0 ? {} : { bindings: options.bindings.slice() }
|
|
9977
|
+
};
|
|
9978
|
+
return this.node({
|
|
9979
|
+
id,
|
|
9980
|
+
type: WorkflowNodeTypes.LLMResponses,
|
|
9981
|
+
input
|
|
9982
|
+
});
|
|
9983
|
+
}
|
|
9984
|
+
joinAll(id) {
|
|
9985
|
+
return this.node({ id, type: WorkflowNodeTypes.JoinAll });
|
|
9986
|
+
}
|
|
9987
|
+
transformJSON(id, input) {
|
|
9988
|
+
return this.node({ id, type: WorkflowNodeTypes.TransformJSON, input });
|
|
9989
|
+
}
|
|
9990
|
+
edge(from, to) {
|
|
9991
|
+
return this.with({ edges: [...this.state.edges, { from, to }] });
|
|
9992
|
+
}
|
|
9993
|
+
output(name, from, pointer) {
|
|
9994
|
+
return this.with({
|
|
9995
|
+
outputs: [
|
|
9996
|
+
...this.state.outputs,
|
|
9997
|
+
{ name, from, ...pointer ? { pointer } : {} }
|
|
9998
|
+
]
|
|
9999
|
+
});
|
|
10000
|
+
}
|
|
10001
|
+
build() {
|
|
10002
|
+
const edges = this.state.edges.slice().sort((a, b) => {
|
|
10003
|
+
const af = String(a.from);
|
|
10004
|
+
const bf = String(b.from);
|
|
10005
|
+
if (af < bf) return -1;
|
|
10006
|
+
if (af > bf) return 1;
|
|
10007
|
+
const at = String(a.to);
|
|
10008
|
+
const bt = String(b.to);
|
|
10009
|
+
if (at < bt) return -1;
|
|
10010
|
+
if (at > bt) return 1;
|
|
10011
|
+
return 0;
|
|
10012
|
+
});
|
|
10013
|
+
const outputs = this.state.outputs.slice().sort((a, b) => {
|
|
10014
|
+
const an = String(a.name);
|
|
10015
|
+
const bn = String(b.name);
|
|
10016
|
+
if (an < bn) return -1;
|
|
10017
|
+
if (an > bn) return 1;
|
|
10018
|
+
const af = String(a.from);
|
|
10019
|
+
const bf = String(b.from);
|
|
10020
|
+
if (af < bf) return -1;
|
|
10021
|
+
if (af > bf) return 1;
|
|
10022
|
+
const ap = a.pointer ?? "";
|
|
10023
|
+
const bp = b.pointer ?? "";
|
|
10024
|
+
if (ap < bp) return -1;
|
|
10025
|
+
if (ap > bp) return 1;
|
|
10026
|
+
return 0;
|
|
10027
|
+
});
|
|
10028
|
+
return {
|
|
10029
|
+
kind: WorkflowKinds.WorkflowV0,
|
|
10030
|
+
...this.state.name ? { name: this.state.name } : {},
|
|
10031
|
+
...this.state.execution ? { execution: this.state.execution } : {},
|
|
10032
|
+
nodes: this.state.nodes.slice(),
|
|
10033
|
+
...edges.length ? { edges } : {},
|
|
10034
|
+
outputs
|
|
10035
|
+
};
|
|
10036
|
+
}
|
|
10037
|
+
};
|
|
10038
|
+
function workflowV0() {
|
|
10039
|
+
return WorkflowBuilderV0.new();
|
|
10040
|
+
}
|
|
10041
|
+
|
|
10042
|
+
// src/workflow_v0.schema.json
|
|
10043
|
+
var workflow_v0_schema_default = {
|
|
10044
|
+
$id: "https://modelrelay.ai/schemas/workflow_v0.schema.json",
|
|
10045
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
10046
|
+
additionalProperties: false,
|
|
10047
|
+
definitions: {
|
|
10048
|
+
edge: {
|
|
10049
|
+
additionalProperties: false,
|
|
10050
|
+
properties: {
|
|
10051
|
+
from: {
|
|
10052
|
+
minLength: 1,
|
|
10053
|
+
type: "string"
|
|
10054
|
+
},
|
|
10055
|
+
to: {
|
|
10056
|
+
minLength: 1,
|
|
10057
|
+
type: "string"
|
|
10058
|
+
}
|
|
10059
|
+
},
|
|
10060
|
+
required: [
|
|
10061
|
+
"from",
|
|
10062
|
+
"to"
|
|
10063
|
+
],
|
|
10064
|
+
type: "object"
|
|
10065
|
+
},
|
|
10066
|
+
llmResponsesBinding: {
|
|
10067
|
+
additionalProperties: false,
|
|
10068
|
+
properties: {
|
|
10069
|
+
encoding: {
|
|
10070
|
+
enum: [
|
|
10071
|
+
"json",
|
|
10072
|
+
"json_string"
|
|
10073
|
+
],
|
|
10074
|
+
type: "string"
|
|
10075
|
+
},
|
|
10076
|
+
from: {
|
|
10077
|
+
minLength: 1,
|
|
10078
|
+
type: "string"
|
|
10079
|
+
},
|
|
10080
|
+
pointer: {
|
|
10081
|
+
pattern: "^(/.*)?$",
|
|
10082
|
+
type: "string"
|
|
10083
|
+
},
|
|
10084
|
+
to: {
|
|
10085
|
+
pattern: "^/.*$",
|
|
10086
|
+
type: "string"
|
|
10087
|
+
}
|
|
10088
|
+
},
|
|
10089
|
+
required: [
|
|
10090
|
+
"from",
|
|
10091
|
+
"to"
|
|
10092
|
+
],
|
|
10093
|
+
type: "object"
|
|
10094
|
+
},
|
|
10095
|
+
node: {
|
|
10096
|
+
additionalProperties: false,
|
|
10097
|
+
oneOf: [
|
|
10098
|
+
{
|
|
10099
|
+
allOf: [
|
|
10100
|
+
{
|
|
10101
|
+
properties: {
|
|
10102
|
+
input: {
|
|
10103
|
+
properties: {
|
|
10104
|
+
bindings: {
|
|
10105
|
+
items: {
|
|
10106
|
+
$ref: "#/definitions/llmResponsesBinding"
|
|
10107
|
+
},
|
|
10108
|
+
type: "array"
|
|
10109
|
+
},
|
|
10110
|
+
request: {
|
|
10111
|
+
type: "object"
|
|
10112
|
+
},
|
|
10113
|
+
stream: {
|
|
10114
|
+
type: "boolean"
|
|
10115
|
+
},
|
|
10116
|
+
tool_execution: {
|
|
10117
|
+
additionalProperties: false,
|
|
10118
|
+
properties: {
|
|
10119
|
+
mode: {
|
|
10120
|
+
default: "server",
|
|
10121
|
+
enum: [
|
|
10122
|
+
"server",
|
|
8999
10123
|
"client"
|
|
9000
10124
|
],
|
|
9001
10125
|
type: "string"
|
|
@@ -9054,182 +10178,1499 @@ var workflow_v0_schema_default = {
|
|
|
9054
10178
|
const: "join.all"
|
|
9055
10179
|
}
|
|
9056
10180
|
}
|
|
9057
|
-
},
|
|
9058
|
-
{
|
|
9059
|
-
allOf: [
|
|
9060
|
-
{
|
|
9061
|
-
properties: {
|
|
9062
|
-
input: {
|
|
9063
|
-
additionalProperties: false,
|
|
9064
|
-
oneOf: [
|
|
9065
|
-
{
|
|
9066
|
-
not: {
|
|
9067
|
-
required: [
|
|
9068
|
-
"merge"
|
|
9069
|
-
]
|
|
9070
|
-
},
|
|
9071
|
-
required: [
|
|
9072
|
-
"object"
|
|
9073
|
-
]
|
|
9074
|
-
},
|
|
9075
|
-
{
|
|
9076
|
-
not: {
|
|
9077
|
-
required: [
|
|
9078
|
-
"object"
|
|
9079
|
-
]
|
|
9080
|
-
},
|
|
9081
|
-
required: [
|
|
9082
|
-
"merge"
|
|
9083
|
-
]
|
|
9084
|
-
}
|
|
9085
|
-
],
|
|
9086
|
-
properties: {
|
|
9087
|
-
merge: {
|
|
9088
|
-
items: {
|
|
9089
|
-
$ref: "#/definitions/transformValue"
|
|
9090
|
-
},
|
|
9091
|
-
minItems: 1,
|
|
9092
|
-
type: "array"
|
|
9093
|
-
},
|
|
9094
|
-
object: {
|
|
9095
|
-
additionalProperties: {
|
|
9096
|
-
$ref: "#/definitions/transformValue"
|
|
9097
|
-
},
|
|
9098
|
-
minProperties: 1,
|
|
9099
|
-
type: "object"
|
|
9100
|
-
}
|
|
9101
|
-
},
|
|
9102
|
-
type: "object"
|
|
9103
|
-
}
|
|
10181
|
+
},
|
|
10182
|
+
{
|
|
10183
|
+
allOf: [
|
|
10184
|
+
{
|
|
10185
|
+
properties: {
|
|
10186
|
+
input: {
|
|
10187
|
+
additionalProperties: false,
|
|
10188
|
+
oneOf: [
|
|
10189
|
+
{
|
|
10190
|
+
not: {
|
|
10191
|
+
required: [
|
|
10192
|
+
"merge"
|
|
10193
|
+
]
|
|
10194
|
+
},
|
|
10195
|
+
required: [
|
|
10196
|
+
"object"
|
|
10197
|
+
]
|
|
10198
|
+
},
|
|
10199
|
+
{
|
|
10200
|
+
not: {
|
|
10201
|
+
required: [
|
|
10202
|
+
"object"
|
|
10203
|
+
]
|
|
10204
|
+
},
|
|
10205
|
+
required: [
|
|
10206
|
+
"merge"
|
|
10207
|
+
]
|
|
10208
|
+
}
|
|
10209
|
+
],
|
|
10210
|
+
properties: {
|
|
10211
|
+
merge: {
|
|
10212
|
+
items: {
|
|
10213
|
+
$ref: "#/definitions/transformValue"
|
|
10214
|
+
},
|
|
10215
|
+
minItems: 1,
|
|
10216
|
+
type: "array"
|
|
10217
|
+
},
|
|
10218
|
+
object: {
|
|
10219
|
+
additionalProperties: {
|
|
10220
|
+
$ref: "#/definitions/transformValue"
|
|
10221
|
+
},
|
|
10222
|
+
minProperties: 1,
|
|
10223
|
+
type: "object"
|
|
10224
|
+
}
|
|
10225
|
+
},
|
|
10226
|
+
type: "object"
|
|
10227
|
+
}
|
|
10228
|
+
}
|
|
10229
|
+
}
|
|
10230
|
+
],
|
|
10231
|
+
properties: {
|
|
10232
|
+
type: {
|
|
10233
|
+
const: "transform.json"
|
|
10234
|
+
}
|
|
10235
|
+
},
|
|
10236
|
+
required: [
|
|
10237
|
+
"input"
|
|
10238
|
+
]
|
|
10239
|
+
}
|
|
10240
|
+
],
|
|
10241
|
+
properties: {
|
|
10242
|
+
id: {
|
|
10243
|
+
minLength: 1,
|
|
10244
|
+
type: "string"
|
|
10245
|
+
},
|
|
10246
|
+
input: {},
|
|
10247
|
+
type: {
|
|
10248
|
+
enum: [
|
|
10249
|
+
"llm.responses",
|
|
10250
|
+
"join.all",
|
|
10251
|
+
"transform.json"
|
|
10252
|
+
],
|
|
10253
|
+
type: "string"
|
|
10254
|
+
}
|
|
10255
|
+
},
|
|
10256
|
+
required: [
|
|
10257
|
+
"id",
|
|
10258
|
+
"type"
|
|
10259
|
+
],
|
|
10260
|
+
type: "object"
|
|
10261
|
+
},
|
|
10262
|
+
output: {
|
|
10263
|
+
additionalProperties: false,
|
|
10264
|
+
properties: {
|
|
10265
|
+
from: {
|
|
10266
|
+
minLength: 1,
|
|
10267
|
+
type: "string"
|
|
10268
|
+
},
|
|
10269
|
+
name: {
|
|
10270
|
+
minLength: 1,
|
|
10271
|
+
type: "string"
|
|
10272
|
+
},
|
|
10273
|
+
pointer: {
|
|
10274
|
+
pattern: "^(/.*)?$",
|
|
10275
|
+
type: "string"
|
|
10276
|
+
}
|
|
10277
|
+
},
|
|
10278
|
+
required: [
|
|
10279
|
+
"name",
|
|
10280
|
+
"from"
|
|
10281
|
+
],
|
|
10282
|
+
type: "object"
|
|
10283
|
+
},
|
|
10284
|
+
transformValue: {
|
|
10285
|
+
additionalProperties: false,
|
|
10286
|
+
properties: {
|
|
10287
|
+
from: {
|
|
10288
|
+
minLength: 1,
|
|
10289
|
+
type: "string"
|
|
10290
|
+
},
|
|
10291
|
+
pointer: {
|
|
10292
|
+
pattern: "^(/.*)?$",
|
|
10293
|
+
type: "string"
|
|
10294
|
+
}
|
|
10295
|
+
},
|
|
10296
|
+
required: [
|
|
10297
|
+
"from"
|
|
10298
|
+
],
|
|
10299
|
+
type: "object"
|
|
10300
|
+
}
|
|
10301
|
+
},
|
|
10302
|
+
properties: {
|
|
10303
|
+
edges: {
|
|
10304
|
+
items: {
|
|
10305
|
+
$ref: "#/definitions/edge"
|
|
10306
|
+
},
|
|
10307
|
+
type: "array"
|
|
10308
|
+
},
|
|
10309
|
+
execution: {
|
|
10310
|
+
additionalProperties: false,
|
|
10311
|
+
properties: {
|
|
10312
|
+
max_parallelism: {
|
|
10313
|
+
minimum: 1,
|
|
10314
|
+
type: "integer"
|
|
10315
|
+
},
|
|
10316
|
+
node_timeout_ms: {
|
|
10317
|
+
minimum: 1,
|
|
10318
|
+
type: "integer"
|
|
10319
|
+
},
|
|
10320
|
+
run_timeout_ms: {
|
|
10321
|
+
minimum: 1,
|
|
10322
|
+
type: "integer"
|
|
10323
|
+
}
|
|
10324
|
+
},
|
|
10325
|
+
type: "object"
|
|
10326
|
+
},
|
|
10327
|
+
kind: {
|
|
10328
|
+
const: "workflow.v0",
|
|
10329
|
+
type: "string"
|
|
10330
|
+
},
|
|
10331
|
+
name: {
|
|
10332
|
+
type: "string"
|
|
10333
|
+
},
|
|
10334
|
+
nodes: {
|
|
10335
|
+
items: {
|
|
10336
|
+
$ref: "#/definitions/node"
|
|
10337
|
+
},
|
|
10338
|
+
minItems: 1,
|
|
10339
|
+
type: "array"
|
|
10340
|
+
},
|
|
10341
|
+
outputs: {
|
|
10342
|
+
items: {
|
|
10343
|
+
$ref: "#/definitions/output"
|
|
10344
|
+
},
|
|
10345
|
+
minItems: 1,
|
|
10346
|
+
type: "array"
|
|
10347
|
+
}
|
|
10348
|
+
},
|
|
10349
|
+
required: [
|
|
10350
|
+
"kind",
|
|
10351
|
+
"nodes",
|
|
10352
|
+
"outputs"
|
|
10353
|
+
],
|
|
10354
|
+
title: "ModelRelay workflow.v0",
|
|
10355
|
+
type: "object"
|
|
10356
|
+
};
|
|
10357
|
+
|
|
10358
|
+
// src/tools_local_fs.ts
|
|
10359
|
+
var import_fs = require("fs");
|
|
10360
|
+
var path = __toESM(require("path"), 1);
|
|
10361
|
+
var import_child_process = require("child_process");
|
|
10362
|
+
var ToolNames = {
|
|
10363
|
+
FS_READ_FILE: "fs.read_file",
|
|
10364
|
+
FS_LIST_FILES: "fs.list_files",
|
|
10365
|
+
FS_SEARCH: "fs.search"
|
|
10366
|
+
};
|
|
10367
|
+
var FSDefaults = {
|
|
10368
|
+
MAX_READ_BYTES: 64e3,
|
|
10369
|
+
HARD_MAX_READ_BYTES: 1e6,
|
|
10370
|
+
MAX_LIST_ENTRIES: 2e3,
|
|
10371
|
+
HARD_MAX_LIST_ENTRIES: 2e4,
|
|
10372
|
+
MAX_SEARCH_MATCHES: 100,
|
|
10373
|
+
HARD_MAX_SEARCH_MATCHES: 2e3,
|
|
10374
|
+
SEARCH_TIMEOUT_MS: 5e3,
|
|
10375
|
+
MAX_SEARCH_BYTES_PER_FILE: 1e6
|
|
10376
|
+
};
|
|
10377
|
+
var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
10378
|
+
".git",
|
|
10379
|
+
"node_modules",
|
|
10380
|
+
"vendor",
|
|
10381
|
+
"dist",
|
|
10382
|
+
"build",
|
|
10383
|
+
".next",
|
|
10384
|
+
"target",
|
|
10385
|
+
".idea",
|
|
10386
|
+
".vscode",
|
|
10387
|
+
"__pycache__",
|
|
10388
|
+
".pytest_cache",
|
|
10389
|
+
"coverage"
|
|
10390
|
+
]);
|
|
10391
|
+
var LocalFSToolPack = class {
|
|
10392
|
+
constructor(options) {
|
|
10393
|
+
this.rgPath = null;
|
|
10394
|
+
this.rgChecked = false;
|
|
10395
|
+
const root = options.root?.trim();
|
|
10396
|
+
if (!root) {
|
|
10397
|
+
throw new Error("LocalFSToolPack: root directory required");
|
|
10398
|
+
}
|
|
10399
|
+
this.rootAbs = path.resolve(root);
|
|
10400
|
+
this.cfg = {
|
|
10401
|
+
ignoreDirs: options.ignoreDirs ?? new Set(DEFAULT_IGNORE_DIRS),
|
|
10402
|
+
maxReadBytes: options.maxReadBytes ?? FSDefaults.MAX_READ_BYTES,
|
|
10403
|
+
hardMaxReadBytes: options.hardMaxReadBytes ?? FSDefaults.HARD_MAX_READ_BYTES,
|
|
10404
|
+
maxListEntries: options.maxListEntries ?? FSDefaults.MAX_LIST_ENTRIES,
|
|
10405
|
+
hardMaxListEntries: options.hardMaxListEntries ?? FSDefaults.HARD_MAX_LIST_ENTRIES,
|
|
10406
|
+
maxSearchMatches: options.maxSearchMatches ?? FSDefaults.MAX_SEARCH_MATCHES,
|
|
10407
|
+
hardMaxSearchMatches: options.hardMaxSearchMatches ?? FSDefaults.HARD_MAX_SEARCH_MATCHES,
|
|
10408
|
+
searchTimeoutMs: options.searchTimeoutMs ?? FSDefaults.SEARCH_TIMEOUT_MS,
|
|
10409
|
+
maxSearchBytesPerFile: options.maxSearchBytesPerFile ?? FSDefaults.MAX_SEARCH_BYTES_PER_FILE
|
|
10410
|
+
};
|
|
10411
|
+
}
|
|
10412
|
+
/**
|
|
10413
|
+
* Returns the tool definitions for LLM requests.
|
|
10414
|
+
* Use these when constructing the tools array for /responses requests.
|
|
10415
|
+
*/
|
|
10416
|
+
getToolDefinitions() {
|
|
10417
|
+
return [
|
|
10418
|
+
{
|
|
10419
|
+
type: "function",
|
|
10420
|
+
function: {
|
|
10421
|
+
name: ToolNames.FS_READ_FILE,
|
|
10422
|
+
description: "Read the contents of a file. Returns the file contents as UTF-8 text.",
|
|
10423
|
+
parameters: {
|
|
10424
|
+
type: "object",
|
|
10425
|
+
properties: {
|
|
10426
|
+
path: {
|
|
10427
|
+
type: "string",
|
|
10428
|
+
description: "Workspace-relative path to the file (e.g., 'src/index.ts')"
|
|
10429
|
+
},
|
|
10430
|
+
max_bytes: {
|
|
10431
|
+
type: "integer",
|
|
10432
|
+
description: `Maximum bytes to read. Default: ${this.cfg.maxReadBytes}, max: ${this.cfg.hardMaxReadBytes}`
|
|
10433
|
+
}
|
|
10434
|
+
},
|
|
10435
|
+
required: ["path"]
|
|
10436
|
+
}
|
|
10437
|
+
}
|
|
10438
|
+
},
|
|
10439
|
+
{
|
|
10440
|
+
type: "function",
|
|
10441
|
+
function: {
|
|
10442
|
+
name: ToolNames.FS_LIST_FILES,
|
|
10443
|
+
description: "List files recursively in a directory. Returns newline-separated workspace-relative paths.",
|
|
10444
|
+
parameters: {
|
|
10445
|
+
type: "object",
|
|
10446
|
+
properties: {
|
|
10447
|
+
path: {
|
|
10448
|
+
type: "string",
|
|
10449
|
+
description: "Workspace-relative directory path. Default: '.' (workspace root)"
|
|
10450
|
+
},
|
|
10451
|
+
max_entries: {
|
|
10452
|
+
type: "integer",
|
|
10453
|
+
description: `Maximum files to list. Default: ${this.cfg.maxListEntries}, max: ${this.cfg.hardMaxListEntries}`
|
|
9104
10454
|
}
|
|
9105
10455
|
}
|
|
9106
|
-
|
|
9107
|
-
properties: {
|
|
9108
|
-
type: {
|
|
9109
|
-
const: "transform.json"
|
|
9110
|
-
}
|
|
9111
|
-
},
|
|
9112
|
-
required: [
|
|
9113
|
-
"input"
|
|
9114
|
-
]
|
|
10456
|
+
}
|
|
9115
10457
|
}
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
|
|
10458
|
+
},
|
|
10459
|
+
{
|
|
10460
|
+
type: "function",
|
|
10461
|
+
function: {
|
|
10462
|
+
name: ToolNames.FS_SEARCH,
|
|
10463
|
+
description: "Search for text matching a regex pattern. Returns matches as 'path:line:content' format.",
|
|
10464
|
+
parameters: {
|
|
10465
|
+
type: "object",
|
|
10466
|
+
properties: {
|
|
10467
|
+
query: {
|
|
10468
|
+
type: "string",
|
|
10469
|
+
description: "Regex pattern to search for"
|
|
10470
|
+
},
|
|
10471
|
+
path: {
|
|
10472
|
+
type: "string",
|
|
10473
|
+
description: "Workspace-relative directory to search. Default: '.' (workspace root)"
|
|
10474
|
+
},
|
|
10475
|
+
max_matches: {
|
|
10476
|
+
type: "integer",
|
|
10477
|
+
description: `Maximum matches to return. Default: ${this.cfg.maxSearchMatches}, max: ${this.cfg.hardMaxSearchMatches}`
|
|
10478
|
+
}
|
|
10479
|
+
},
|
|
10480
|
+
required: ["query"]
|
|
10481
|
+
}
|
|
10482
|
+
}
|
|
10483
|
+
}
|
|
10484
|
+
];
|
|
10485
|
+
}
|
|
10486
|
+
/**
|
|
10487
|
+
* Registers handlers into an existing ToolRegistry.
|
|
10488
|
+
* @param registry - The registry to register into
|
|
10489
|
+
* @returns The registry for chaining
|
|
10490
|
+
*/
|
|
10491
|
+
registerInto(registry) {
|
|
10492
|
+
registry.register(ToolNames.FS_READ_FILE, this.readFile.bind(this));
|
|
10493
|
+
registry.register(ToolNames.FS_LIST_FILES, this.listFiles.bind(this));
|
|
10494
|
+
registry.register(ToolNames.FS_SEARCH, this.search.bind(this));
|
|
10495
|
+
return registry;
|
|
10496
|
+
}
|
|
10497
|
+
/**
|
|
10498
|
+
* Creates a new ToolRegistry with fs.* tools pre-registered.
|
|
10499
|
+
*/
|
|
10500
|
+
toRegistry() {
|
|
10501
|
+
return this.registerInto(new ToolRegistry());
|
|
10502
|
+
}
|
|
10503
|
+
// ========================================================================
|
|
10504
|
+
// Tool Handlers
|
|
10505
|
+
// ========================================================================
|
|
10506
|
+
async readFile(_args, call) {
|
|
10507
|
+
const args = this.parseArgs(call, ["path"]);
|
|
10508
|
+
const func = call.function;
|
|
10509
|
+
const relPath = this.requireString(args, "path", call);
|
|
10510
|
+
const requestedMax = this.optionalPositiveInt(args, "max_bytes", call);
|
|
10511
|
+
let maxBytes = this.cfg.maxReadBytes;
|
|
10512
|
+
if (requestedMax !== void 0) {
|
|
10513
|
+
if (requestedMax > this.cfg.hardMaxReadBytes) {
|
|
10514
|
+
throw new ToolArgumentError({
|
|
10515
|
+
message: `max_bytes exceeds hard cap (${this.cfg.hardMaxReadBytes})`,
|
|
10516
|
+
toolCallId: call.id,
|
|
10517
|
+
toolName: func.name,
|
|
10518
|
+
rawArguments: func.arguments
|
|
10519
|
+
});
|
|
10520
|
+
}
|
|
10521
|
+
maxBytes = requestedMax;
|
|
10522
|
+
}
|
|
10523
|
+
const absPath = await this.resolveAndValidatePath(relPath, call);
|
|
10524
|
+
const stat = await import_fs.promises.stat(absPath);
|
|
10525
|
+
if (stat.isDirectory()) {
|
|
10526
|
+
throw new Error(`fs.read_file: path is a directory: ${relPath}`);
|
|
10527
|
+
}
|
|
10528
|
+
if (stat.size > maxBytes) {
|
|
10529
|
+
throw new Error(`fs.read_file: file exceeds max_bytes (${maxBytes})`);
|
|
10530
|
+
}
|
|
10531
|
+
const data = await import_fs.promises.readFile(absPath);
|
|
10532
|
+
if (!this.isValidUtf8(data)) {
|
|
10533
|
+
throw new Error(`fs.read_file: file is not valid UTF-8: ${relPath}`);
|
|
10534
|
+
}
|
|
10535
|
+
return data.toString("utf-8");
|
|
10536
|
+
}
|
|
10537
|
+
async listFiles(_args, call) {
|
|
10538
|
+
const args = this.parseArgs(call, []);
|
|
10539
|
+
const func = call.function;
|
|
10540
|
+
const startPath = this.optionalString(args, "path", call)?.trim() || ".";
|
|
10541
|
+
let maxEntries = this.cfg.maxListEntries;
|
|
10542
|
+
const requestedMax = this.optionalPositiveInt(args, "max_entries", call);
|
|
10543
|
+
if (requestedMax !== void 0) {
|
|
10544
|
+
if (requestedMax > this.cfg.hardMaxListEntries) {
|
|
10545
|
+
throw new ToolArgumentError({
|
|
10546
|
+
message: `max_entries exceeds hard cap (${this.cfg.hardMaxListEntries})`,
|
|
10547
|
+
toolCallId: call.id,
|
|
10548
|
+
toolName: func.name,
|
|
10549
|
+
rawArguments: func.arguments
|
|
10550
|
+
});
|
|
10551
|
+
}
|
|
10552
|
+
maxEntries = requestedMax;
|
|
10553
|
+
}
|
|
10554
|
+
const absPath = await this.resolveAndValidatePath(startPath, call);
|
|
10555
|
+
let rootReal;
|
|
10556
|
+
try {
|
|
10557
|
+
rootReal = await import_fs.promises.realpath(this.rootAbs);
|
|
10558
|
+
} catch {
|
|
10559
|
+
rootReal = this.rootAbs;
|
|
10560
|
+
}
|
|
10561
|
+
const stat = await import_fs.promises.stat(absPath);
|
|
10562
|
+
if (!stat.isDirectory()) {
|
|
10563
|
+
throw new Error(`fs.list_files: path is not a directory: ${startPath}`);
|
|
10564
|
+
}
|
|
10565
|
+
const files = [];
|
|
10566
|
+
await this.walkDir(absPath, async (filePath, dirent) => {
|
|
10567
|
+
if (files.length >= maxEntries) {
|
|
10568
|
+
return false;
|
|
10569
|
+
}
|
|
10570
|
+
if (dirent.isDirectory()) {
|
|
10571
|
+
if (this.cfg.ignoreDirs.has(dirent.name)) {
|
|
10572
|
+
return false;
|
|
10573
|
+
}
|
|
10574
|
+
return true;
|
|
10575
|
+
}
|
|
10576
|
+
if (dirent.isFile()) {
|
|
10577
|
+
const relPath = path.relative(rootReal, filePath);
|
|
10578
|
+
files.push(relPath.split(path.sep).join("/"));
|
|
10579
|
+
}
|
|
10580
|
+
return true;
|
|
10581
|
+
});
|
|
10582
|
+
return files.join("\n");
|
|
10583
|
+
}
|
|
10584
|
+
async search(_args, call) {
|
|
10585
|
+
const args = this.parseArgs(call, ["query"]);
|
|
10586
|
+
const func = call.function;
|
|
10587
|
+
const query = this.requireString(args, "query", call);
|
|
10588
|
+
const startPath = this.optionalString(args, "path", call)?.trim() || ".";
|
|
10589
|
+
let maxMatches = this.cfg.maxSearchMatches;
|
|
10590
|
+
const requestedMax = this.optionalPositiveInt(args, "max_matches", call);
|
|
10591
|
+
if (requestedMax !== void 0) {
|
|
10592
|
+
if (requestedMax > this.cfg.hardMaxSearchMatches) {
|
|
10593
|
+
throw new ToolArgumentError({
|
|
10594
|
+
message: `max_matches exceeds hard cap (${this.cfg.hardMaxSearchMatches})`,
|
|
10595
|
+
toolCallId: call.id,
|
|
10596
|
+
toolName: func.name,
|
|
10597
|
+
rawArguments: func.arguments
|
|
10598
|
+
});
|
|
10599
|
+
}
|
|
10600
|
+
maxMatches = requestedMax;
|
|
10601
|
+
}
|
|
10602
|
+
const absPath = await this.resolveAndValidatePath(startPath, call);
|
|
10603
|
+
const rgPath = await this.detectRipgrep();
|
|
10604
|
+
if (rgPath) {
|
|
10605
|
+
return this.searchWithRipgrep(
|
|
10606
|
+
rgPath,
|
|
10607
|
+
query,
|
|
10608
|
+
absPath,
|
|
10609
|
+
maxMatches
|
|
10610
|
+
);
|
|
10611
|
+
}
|
|
10612
|
+
return this.searchWithJS(query, absPath, maxMatches, call);
|
|
10613
|
+
}
|
|
10614
|
+
// ========================================================================
|
|
10615
|
+
// Path Safety
|
|
10616
|
+
// ========================================================================
|
|
10617
|
+
/**
|
|
10618
|
+
* Resolves a workspace-relative path and validates it stays within the sandbox.
|
|
10619
|
+
* @throws {ToolArgumentError} if path is invalid
|
|
10620
|
+
* @throws {PathEscapeError} if resolved path escapes root
|
|
10621
|
+
*/
|
|
10622
|
+
async resolveAndValidatePath(relPath, call) {
|
|
10623
|
+
const func = call.function;
|
|
10624
|
+
const cleanRel = relPath.trim();
|
|
10625
|
+
if (!cleanRel) {
|
|
10626
|
+
throw new ToolArgumentError({
|
|
10627
|
+
message: "path cannot be empty",
|
|
10628
|
+
toolCallId: call.id,
|
|
10629
|
+
toolName: func.name,
|
|
10630
|
+
rawArguments: func.arguments
|
|
10631
|
+
});
|
|
10632
|
+
}
|
|
10633
|
+
if (path.isAbsolute(cleanRel)) {
|
|
10634
|
+
throw new ToolArgumentError({
|
|
10635
|
+
message: "path must be workspace-relative (not absolute)",
|
|
10636
|
+
toolCallId: call.id,
|
|
10637
|
+
toolName: func.name,
|
|
10638
|
+
rawArguments: func.arguments
|
|
10639
|
+
});
|
|
10640
|
+
}
|
|
10641
|
+
const normalized = path.normalize(cleanRel);
|
|
10642
|
+
if (normalized.startsWith("..") || normalized.startsWith(`.${path.sep}..`)) {
|
|
10643
|
+
throw new ToolArgumentError({
|
|
10644
|
+
message: "path must not escape the workspace root",
|
|
10645
|
+
toolCallId: call.id,
|
|
10646
|
+
toolName: func.name,
|
|
10647
|
+
rawArguments: func.arguments
|
|
10648
|
+
});
|
|
10649
|
+
}
|
|
10650
|
+
const target = path.join(this.rootAbs, normalized);
|
|
10651
|
+
let rootReal;
|
|
10652
|
+
try {
|
|
10653
|
+
rootReal = await import_fs.promises.realpath(this.rootAbs);
|
|
10654
|
+
} catch {
|
|
10655
|
+
rootReal = this.rootAbs;
|
|
10656
|
+
}
|
|
10657
|
+
let resolved;
|
|
10658
|
+
try {
|
|
10659
|
+
resolved = await import_fs.promises.realpath(target);
|
|
10660
|
+
} catch (err) {
|
|
10661
|
+
resolved = path.join(rootReal, normalized);
|
|
10662
|
+
}
|
|
10663
|
+
const relFromRoot = path.relative(rootReal, resolved);
|
|
10664
|
+
if (relFromRoot.startsWith("..") || relFromRoot.startsWith(`.${path.sep}..`) || path.isAbsolute(relFromRoot)) {
|
|
10665
|
+
throw new PathEscapeError({
|
|
10666
|
+
requestedPath: relPath,
|
|
10667
|
+
resolvedPath: resolved
|
|
10668
|
+
});
|
|
10669
|
+
}
|
|
10670
|
+
return resolved;
|
|
10671
|
+
}
|
|
10672
|
+
// ========================================================================
|
|
10673
|
+
// Ripgrep Search
|
|
10674
|
+
// ========================================================================
|
|
10675
|
+
async detectRipgrep() {
|
|
10676
|
+
if (this.rgChecked) {
|
|
10677
|
+
return this.rgPath;
|
|
10678
|
+
}
|
|
10679
|
+
this.rgChecked = true;
|
|
10680
|
+
return new Promise((resolve2) => {
|
|
10681
|
+
const proc = (0, import_child_process.spawn)("rg", ["--version"], { stdio: "ignore" });
|
|
10682
|
+
proc.on("error", () => {
|
|
10683
|
+
this.rgPath = null;
|
|
10684
|
+
resolve2(null);
|
|
10685
|
+
});
|
|
10686
|
+
proc.on("close", (code) => {
|
|
10687
|
+
if (code === 0) {
|
|
10688
|
+
this.rgPath = "rg";
|
|
10689
|
+
resolve2("rg");
|
|
10690
|
+
} else {
|
|
10691
|
+
this.rgPath = null;
|
|
10692
|
+
resolve2(null);
|
|
10693
|
+
}
|
|
10694
|
+
});
|
|
10695
|
+
});
|
|
10696
|
+
}
|
|
10697
|
+
async searchWithRipgrep(rgPath, query, dirAbs, maxMatches) {
|
|
10698
|
+
return new Promise((resolve2, reject) => {
|
|
10699
|
+
const args = ["--line-number", "--no-heading", "--color=never"];
|
|
10700
|
+
for (const name of this.cfg.ignoreDirs) {
|
|
10701
|
+
args.push("--glob", `!**/${name}/**`);
|
|
10702
|
+
}
|
|
10703
|
+
args.push(query, dirAbs);
|
|
10704
|
+
const proc = (0, import_child_process.spawn)(rgPath, args, {
|
|
10705
|
+
timeout: this.cfg.searchTimeoutMs
|
|
10706
|
+
});
|
|
10707
|
+
const lines = [];
|
|
10708
|
+
let stderr = "";
|
|
10709
|
+
let killed = false;
|
|
10710
|
+
proc.stdout.on("data", (chunk) => {
|
|
10711
|
+
if (killed) return;
|
|
10712
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf-8");
|
|
10713
|
+
const newLines = text.split("\n").filter((l) => l.trim());
|
|
10714
|
+
for (const line of newLines) {
|
|
10715
|
+
lines.push(this.normalizeRipgrepLine(line));
|
|
10716
|
+
if (lines.length >= maxMatches) {
|
|
10717
|
+
killed = true;
|
|
10718
|
+
proc.kill();
|
|
10719
|
+
break;
|
|
10720
|
+
}
|
|
10721
|
+
}
|
|
10722
|
+
});
|
|
10723
|
+
proc.stderr.on("data", (chunk) => {
|
|
10724
|
+
stderr += typeof chunk === "string" ? chunk : chunk.toString("utf-8");
|
|
10725
|
+
});
|
|
10726
|
+
proc.on("error", (err) => {
|
|
10727
|
+
reject(new Error(`fs.search: ripgrep error: ${err.message}`));
|
|
10728
|
+
});
|
|
10729
|
+
proc.on("close", (code) => {
|
|
10730
|
+
if (killed) {
|
|
10731
|
+
resolve2(lines.join("\n"));
|
|
10732
|
+
return;
|
|
10733
|
+
}
|
|
10734
|
+
if (code === 0 || code === 1) {
|
|
10735
|
+
resolve2(lines.join("\n"));
|
|
10736
|
+
} else if (code === 2 && stderr.toLowerCase().includes("regex")) {
|
|
10737
|
+
reject(
|
|
10738
|
+
new ToolArgumentError({
|
|
10739
|
+
message: `invalid query regex: ${stderr.trim()}`,
|
|
10740
|
+
toolCallId: "",
|
|
10741
|
+
toolName: ToolNames.FS_SEARCH,
|
|
10742
|
+
rawArguments: ""
|
|
10743
|
+
})
|
|
10744
|
+
);
|
|
10745
|
+
} else if (stderr) {
|
|
10746
|
+
reject(new Error(`fs.search: ripgrep failed: ${stderr.trim()}`));
|
|
10747
|
+
} else {
|
|
10748
|
+
resolve2(lines.join("\n"));
|
|
10749
|
+
}
|
|
10750
|
+
});
|
|
10751
|
+
});
|
|
10752
|
+
}
|
|
10753
|
+
normalizeRipgrepLine(line) {
|
|
10754
|
+
const trimmed = line.trim();
|
|
10755
|
+
if (!trimmed || !trimmed.includes(":")) {
|
|
10756
|
+
return trimmed;
|
|
10757
|
+
}
|
|
10758
|
+
const colonIdx = trimmed.indexOf(":");
|
|
10759
|
+
const filePath = trimmed.slice(0, colonIdx);
|
|
10760
|
+
const rest = trimmed.slice(colonIdx + 1);
|
|
10761
|
+
if (path.isAbsolute(filePath)) {
|
|
10762
|
+
const rel = path.relative(this.rootAbs, filePath);
|
|
10763
|
+
if (!rel.startsWith("..")) {
|
|
10764
|
+
return rel.split(path.sep).join("/") + ":" + rest;
|
|
10765
|
+
}
|
|
10766
|
+
}
|
|
10767
|
+
return filePath.split(path.sep).join("/") + ":" + rest;
|
|
10768
|
+
}
|
|
10769
|
+
// ========================================================================
|
|
10770
|
+
// JavaScript Fallback Search
|
|
10771
|
+
// ========================================================================
|
|
10772
|
+
async searchWithJS(query, dirAbs, maxMatches, call) {
|
|
10773
|
+
const func = call.function;
|
|
10774
|
+
let regex;
|
|
10775
|
+
try {
|
|
10776
|
+
regex = new RegExp(query);
|
|
10777
|
+
} catch (err) {
|
|
10778
|
+
throw new ToolArgumentError({
|
|
10779
|
+
message: `invalid query regex: ${err.message}`,
|
|
10780
|
+
toolCallId: call.id,
|
|
10781
|
+
toolName: func.name,
|
|
10782
|
+
rawArguments: func.arguments
|
|
10783
|
+
});
|
|
10784
|
+
}
|
|
10785
|
+
const matches = [];
|
|
10786
|
+
const deadline = Date.now() + this.cfg.searchTimeoutMs;
|
|
10787
|
+
await this.walkDir(dirAbs, async (filePath, dirent) => {
|
|
10788
|
+
if (Date.now() > deadline) {
|
|
10789
|
+
return false;
|
|
10790
|
+
}
|
|
10791
|
+
if (matches.length >= maxMatches) {
|
|
10792
|
+
return false;
|
|
10793
|
+
}
|
|
10794
|
+
if (dirent.isDirectory()) {
|
|
10795
|
+
if (this.cfg.ignoreDirs.has(dirent.name)) {
|
|
10796
|
+
return false;
|
|
10797
|
+
}
|
|
10798
|
+
return true;
|
|
10799
|
+
}
|
|
10800
|
+
if (!dirent.isFile()) {
|
|
10801
|
+
return true;
|
|
10802
|
+
}
|
|
10803
|
+
try {
|
|
10804
|
+
const stat = await import_fs.promises.stat(filePath);
|
|
10805
|
+
if (stat.size > this.cfg.maxSearchBytesPerFile) {
|
|
10806
|
+
return true;
|
|
10807
|
+
}
|
|
10808
|
+
} catch {
|
|
10809
|
+
return true;
|
|
10810
|
+
}
|
|
10811
|
+
try {
|
|
10812
|
+
const content = await import_fs.promises.readFile(filePath, "utf-8");
|
|
10813
|
+
const lines = content.split("\n");
|
|
10814
|
+
for (let i = 0; i < lines.length && matches.length < maxMatches; i++) {
|
|
10815
|
+
if (regex.test(lines[i])) {
|
|
10816
|
+
const relPath = path.relative(this.rootAbs, filePath);
|
|
10817
|
+
const normalizedPath = relPath.split(path.sep).join("/");
|
|
10818
|
+
matches.push(`${normalizedPath}:${i + 1}:${lines[i]}`);
|
|
10819
|
+
}
|
|
10820
|
+
}
|
|
10821
|
+
} catch {
|
|
10822
|
+
}
|
|
10823
|
+
return true;
|
|
10824
|
+
});
|
|
10825
|
+
return matches.join("\n");
|
|
10826
|
+
}
|
|
10827
|
+
// ========================================================================
|
|
10828
|
+
// Helpers
|
|
10829
|
+
// ========================================================================
|
|
10830
|
+
parseArgs(call, required) {
|
|
10831
|
+
const func = call.function;
|
|
10832
|
+
if (!func) {
|
|
10833
|
+
throw new ToolArgumentError({
|
|
10834
|
+
message: "tool call missing function",
|
|
10835
|
+
toolCallId: call.id,
|
|
10836
|
+
toolName: "",
|
|
10837
|
+
rawArguments: ""
|
|
10838
|
+
});
|
|
10839
|
+
}
|
|
10840
|
+
const rawArgs = func.arguments || "{}";
|
|
10841
|
+
let parsed;
|
|
10842
|
+
try {
|
|
10843
|
+
parsed = JSON.parse(rawArgs);
|
|
10844
|
+
} catch (err) {
|
|
10845
|
+
throw new ToolArgumentError({
|
|
10846
|
+
message: `invalid JSON arguments: ${err.message}`,
|
|
10847
|
+
toolCallId: call.id,
|
|
10848
|
+
toolName: func.name,
|
|
10849
|
+
rawArguments: rawArgs
|
|
10850
|
+
});
|
|
10851
|
+
}
|
|
10852
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
10853
|
+
throw new ToolArgumentError({
|
|
10854
|
+
message: "arguments must be an object",
|
|
10855
|
+
toolCallId: call.id,
|
|
10856
|
+
toolName: func.name,
|
|
10857
|
+
rawArguments: rawArgs
|
|
10858
|
+
});
|
|
10859
|
+
}
|
|
10860
|
+
const args = parsed;
|
|
10861
|
+
for (const key of required) {
|
|
10862
|
+
const value = args[key];
|
|
10863
|
+
if (value === void 0 || value === null || value === "") {
|
|
10864
|
+
throw new ToolArgumentError({
|
|
10865
|
+
message: `${key} is required`,
|
|
10866
|
+
toolCallId: call.id,
|
|
10867
|
+
toolName: func.name,
|
|
10868
|
+
rawArguments: rawArgs
|
|
10869
|
+
});
|
|
10870
|
+
}
|
|
10871
|
+
}
|
|
10872
|
+
return args;
|
|
10873
|
+
}
|
|
10874
|
+
toolArgumentError(call, message) {
|
|
10875
|
+
const func = call.function;
|
|
10876
|
+
throw new ToolArgumentError({
|
|
10877
|
+
message,
|
|
10878
|
+
toolCallId: call.id,
|
|
10879
|
+
toolName: func?.name ?? "",
|
|
10880
|
+
rawArguments: func?.arguments ?? ""
|
|
10881
|
+
});
|
|
10882
|
+
}
|
|
10883
|
+
requireString(args, key, call) {
|
|
10884
|
+
const value = args[key];
|
|
10885
|
+
if (typeof value !== "string") {
|
|
10886
|
+
this.toolArgumentError(call, `${key} must be a string`);
|
|
10887
|
+
}
|
|
10888
|
+
if (value.trim() === "") {
|
|
10889
|
+
this.toolArgumentError(call, `${key} is required`);
|
|
10890
|
+
}
|
|
10891
|
+
return value;
|
|
10892
|
+
}
|
|
10893
|
+
optionalString(args, key, call) {
|
|
10894
|
+
const value = args[key];
|
|
10895
|
+
if (value === void 0 || value === null) {
|
|
10896
|
+
return void 0;
|
|
10897
|
+
}
|
|
10898
|
+
if (typeof value !== "string") {
|
|
10899
|
+
this.toolArgumentError(call, `${key} must be a string`);
|
|
10900
|
+
}
|
|
10901
|
+
const trimmed = value.trim();
|
|
10902
|
+
if (trimmed === "") {
|
|
10903
|
+
return void 0;
|
|
10904
|
+
}
|
|
10905
|
+
return value;
|
|
10906
|
+
}
|
|
10907
|
+
optionalPositiveInt(args, key, call) {
|
|
10908
|
+
const value = args[key];
|
|
10909
|
+
if (value === void 0 || value === null) {
|
|
10910
|
+
return void 0;
|
|
10911
|
+
}
|
|
10912
|
+
if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value)) {
|
|
10913
|
+
this.toolArgumentError(call, `${key} must be an integer`);
|
|
10914
|
+
}
|
|
10915
|
+
if (value <= 0) {
|
|
10916
|
+
this.toolArgumentError(call, `${key} must be > 0`);
|
|
10917
|
+
}
|
|
10918
|
+
return value;
|
|
10919
|
+
}
|
|
10920
|
+
isValidUtf8(buffer) {
|
|
10921
|
+
try {
|
|
10922
|
+
const text = buffer.toString("utf-8");
|
|
10923
|
+
return !text.includes("\uFFFD");
|
|
10924
|
+
} catch {
|
|
10925
|
+
return false;
|
|
10926
|
+
}
|
|
10927
|
+
}
|
|
10928
|
+
/**
|
|
10929
|
+
* Recursively walks a directory, calling visitor for each entry.
|
|
10930
|
+
* Visitor returns true to continue, false to skip (for dirs) or stop.
|
|
10931
|
+
*/
|
|
10932
|
+
async walkDir(dir, visitor) {
|
|
10933
|
+
const entries = await import_fs.promises.readdir(dir, { withFileTypes: true });
|
|
10934
|
+
for (const entry of entries) {
|
|
10935
|
+
const fullPath = path.join(dir, entry.name);
|
|
10936
|
+
const shouldContinue = await visitor(fullPath, entry);
|
|
10937
|
+
if (!shouldContinue) {
|
|
10938
|
+
if (entry.isDirectory()) {
|
|
10939
|
+
continue;
|
|
10940
|
+
}
|
|
10941
|
+
return;
|
|
10942
|
+
}
|
|
10943
|
+
if (entry.isDirectory()) {
|
|
10944
|
+
await this.walkDir(fullPath, visitor);
|
|
10945
|
+
}
|
|
10946
|
+
}
|
|
10947
|
+
}
|
|
10948
|
+
};
|
|
10949
|
+
function createLocalFSToolPack(options) {
|
|
10950
|
+
return new LocalFSToolPack(options);
|
|
10951
|
+
}
|
|
10952
|
+
function createLocalFSTools(options) {
|
|
10953
|
+
return createLocalFSToolPack(options).toRegistry();
|
|
10954
|
+
}
|
|
10955
|
+
|
|
10956
|
+
// src/tools_browser.ts
|
|
10957
|
+
var BrowserToolNames = {
|
|
10958
|
+
/** Navigate to a URL and return accessibility tree */
|
|
10959
|
+
NAVIGATE: "browser.navigate",
|
|
10960
|
+
/** Click an element by accessible name/role */
|
|
10961
|
+
CLICK: "browser.click",
|
|
10962
|
+
/** Type text into an input field */
|
|
10963
|
+
TYPE: "browser.type",
|
|
10964
|
+
/** Get current accessibility tree */
|
|
10965
|
+
SNAPSHOT: "browser.snapshot",
|
|
10966
|
+
/** Scroll the page */
|
|
10967
|
+
SCROLL: "browser.scroll",
|
|
10968
|
+
/** Capture a screenshot */
|
|
10969
|
+
SCREENSHOT: "browser.screenshot",
|
|
10970
|
+
/** Extract data using CSS selectors */
|
|
10971
|
+
EXTRACT: "browser.extract"
|
|
10972
|
+
};
|
|
10973
|
+
var BrowserDefaults = {
|
|
10974
|
+
/** Navigation timeout in milliseconds */
|
|
10975
|
+
NAVIGATION_TIMEOUT_MS: 3e4,
|
|
10976
|
+
/** Action timeout in milliseconds */
|
|
10977
|
+
ACTION_TIMEOUT_MS: 5e3,
|
|
10978
|
+
/** Maximum nodes to include in accessibility tree output */
|
|
10979
|
+
MAX_SNAPSHOT_NODES: 500,
|
|
10980
|
+
/** Maximum screenshot size in bytes */
|
|
10981
|
+
MAX_SCREENSHOT_BYTES: 5e6
|
|
10982
|
+
};
|
|
10983
|
+
var BrowserToolPack = class {
|
|
10984
|
+
constructor(options = {}) {
|
|
10985
|
+
this.browser = null;
|
|
10986
|
+
this.context = null;
|
|
10987
|
+
this.page = null;
|
|
10988
|
+
this.cdpSession = null;
|
|
10989
|
+
this.ownsBrowser = false;
|
|
10990
|
+
this.ownsContext = false;
|
|
10991
|
+
this.cfg = {
|
|
10992
|
+
allowedDomains: options.allowedDomains ?? [],
|
|
10993
|
+
blockedDomains: options.blockedDomains ?? [],
|
|
10994
|
+
navigationTimeoutMs: options.navigationTimeoutMs ?? BrowserDefaults.NAVIGATION_TIMEOUT_MS,
|
|
10995
|
+
actionTimeoutMs: options.actionTimeoutMs ?? BrowserDefaults.ACTION_TIMEOUT_MS,
|
|
10996
|
+
maxSnapshotNodes: options.maxSnapshotNodes ?? BrowserDefaults.MAX_SNAPSHOT_NODES,
|
|
10997
|
+
headless: options.headless ?? true
|
|
10998
|
+
};
|
|
10999
|
+
if (options.browser) {
|
|
11000
|
+
this.browser = options.browser;
|
|
11001
|
+
this.ownsBrowser = false;
|
|
11002
|
+
}
|
|
11003
|
+
if (options.context) {
|
|
11004
|
+
this.context = options.context;
|
|
11005
|
+
this.ownsContext = false;
|
|
11006
|
+
}
|
|
11007
|
+
}
|
|
11008
|
+
/**
|
|
11009
|
+
* Initialize the browser. Must be called before using any tools.
|
|
11010
|
+
*/
|
|
11011
|
+
async initialize() {
|
|
11012
|
+
if (this.page) {
|
|
11013
|
+
return;
|
|
11014
|
+
}
|
|
11015
|
+
const { chromium } = await import("playwright");
|
|
11016
|
+
if (!this.browser) {
|
|
11017
|
+
this.browser = await chromium.launch({
|
|
11018
|
+
headless: this.cfg.headless
|
|
11019
|
+
});
|
|
11020
|
+
this.ownsBrowser = true;
|
|
11021
|
+
}
|
|
11022
|
+
if (!this.context) {
|
|
11023
|
+
this.context = await this.browser.newContext();
|
|
11024
|
+
this.ownsContext = true;
|
|
11025
|
+
}
|
|
11026
|
+
this.page = await this.context.newPage();
|
|
11027
|
+
}
|
|
11028
|
+
/**
|
|
11029
|
+
* Close the browser and clean up resources.
|
|
11030
|
+
*/
|
|
11031
|
+
async close() {
|
|
11032
|
+
if (this.cdpSession) {
|
|
11033
|
+
await this.cdpSession.detach().catch(() => {
|
|
11034
|
+
});
|
|
11035
|
+
this.cdpSession = null;
|
|
11036
|
+
}
|
|
11037
|
+
if (this.page) {
|
|
11038
|
+
await this.page.close().catch(() => {
|
|
11039
|
+
});
|
|
11040
|
+
this.page = null;
|
|
11041
|
+
}
|
|
11042
|
+
if (this.ownsContext && this.context) {
|
|
11043
|
+
await this.context.close().catch(() => {
|
|
11044
|
+
});
|
|
11045
|
+
this.context = null;
|
|
11046
|
+
}
|
|
11047
|
+
if (this.ownsBrowser && this.browser) {
|
|
11048
|
+
await this.browser.close().catch(() => {
|
|
11049
|
+
});
|
|
11050
|
+
this.browser = null;
|
|
11051
|
+
}
|
|
11052
|
+
}
|
|
11053
|
+
/**
|
|
11054
|
+
* Get tool definitions for use with LLM APIs.
|
|
11055
|
+
*/
|
|
11056
|
+
getToolDefinitions() {
|
|
11057
|
+
return [
|
|
11058
|
+
{
|
|
11059
|
+
type: ToolTypes.Function,
|
|
11060
|
+
function: {
|
|
11061
|
+
name: BrowserToolNames.NAVIGATE,
|
|
11062
|
+
description: "Navigate to a URL and return the page's accessibility tree. The tree shows interactive elements (buttons, links, inputs) with their accessible names.",
|
|
11063
|
+
parameters: {
|
|
11064
|
+
type: "object",
|
|
11065
|
+
properties: {
|
|
11066
|
+
url: {
|
|
11067
|
+
type: "string",
|
|
11068
|
+
description: "The URL to navigate to (must be http/https)"
|
|
11069
|
+
},
|
|
11070
|
+
waitUntil: {
|
|
11071
|
+
type: "string",
|
|
11072
|
+
enum: ["load", "domcontentloaded", "networkidle"],
|
|
11073
|
+
description: "When to consider navigation complete. Default: domcontentloaded"
|
|
11074
|
+
}
|
|
11075
|
+
},
|
|
11076
|
+
required: ["url"]
|
|
11077
|
+
}
|
|
9130
11078
|
}
|
|
9131
11079
|
},
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9138
|
-
|
|
9139
|
-
|
|
9140
|
-
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
|
|
9144
|
-
|
|
9145
|
-
|
|
9146
|
-
|
|
9147
|
-
|
|
9148
|
-
|
|
9149
|
-
|
|
9150
|
-
|
|
9151
|
-
|
|
11080
|
+
{
|
|
11081
|
+
type: ToolTypes.Function,
|
|
11082
|
+
function: {
|
|
11083
|
+
name: BrowserToolNames.CLICK,
|
|
11084
|
+
description: "Click an element by its accessible name. Returns updated accessibility tree.",
|
|
11085
|
+
parameters: {
|
|
11086
|
+
type: "object",
|
|
11087
|
+
properties: {
|
|
11088
|
+
name: {
|
|
11089
|
+
type: "string",
|
|
11090
|
+
description: "The accessible name of the element (from button text, aria-label, etc.)"
|
|
11091
|
+
},
|
|
11092
|
+
role: {
|
|
11093
|
+
type: "string",
|
|
11094
|
+
enum: [
|
|
11095
|
+
"button",
|
|
11096
|
+
"link",
|
|
11097
|
+
"menuitem",
|
|
11098
|
+
"checkbox",
|
|
11099
|
+
"radio",
|
|
11100
|
+
"tab"
|
|
11101
|
+
],
|
|
11102
|
+
description: "ARIA role to match. If omitted, searches buttons, links, and menuitems."
|
|
11103
|
+
}
|
|
11104
|
+
},
|
|
11105
|
+
required: ["name"]
|
|
11106
|
+
}
|
|
9152
11107
|
}
|
|
9153
11108
|
},
|
|
9154
|
-
|
|
9155
|
-
|
|
9156
|
-
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
|
|
9164
|
-
|
|
9165
|
-
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
|
|
9169
|
-
|
|
11109
|
+
{
|
|
11110
|
+
type: ToolTypes.Function,
|
|
11111
|
+
function: {
|
|
11112
|
+
name: BrowserToolNames.TYPE,
|
|
11113
|
+
description: "Type text into an input field identified by accessible name.",
|
|
11114
|
+
parameters: {
|
|
11115
|
+
type: "object",
|
|
11116
|
+
properties: {
|
|
11117
|
+
name: {
|
|
11118
|
+
type: "string",
|
|
11119
|
+
description: "The accessible name of the input (from label, aria-label, placeholder)"
|
|
11120
|
+
},
|
|
11121
|
+
text: {
|
|
11122
|
+
type: "string",
|
|
11123
|
+
description: "The text to type"
|
|
11124
|
+
},
|
|
11125
|
+
role: {
|
|
11126
|
+
type: "string",
|
|
11127
|
+
enum: ["textbox", "searchbox", "combobox"],
|
|
11128
|
+
description: "ARIA role. Default: textbox"
|
|
11129
|
+
}
|
|
11130
|
+
},
|
|
11131
|
+
required: ["name", "text"]
|
|
11132
|
+
}
|
|
9170
11133
|
}
|
|
9171
11134
|
},
|
|
9172
|
-
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
|
|
9181
|
-
|
|
11135
|
+
{
|
|
11136
|
+
type: ToolTypes.Function,
|
|
11137
|
+
function: {
|
|
11138
|
+
name: BrowserToolNames.SNAPSHOT,
|
|
11139
|
+
description: "Get the current page's accessibility tree without navigating.",
|
|
11140
|
+
parameters: {
|
|
11141
|
+
type: "object",
|
|
11142
|
+
properties: {}
|
|
11143
|
+
}
|
|
11144
|
+
}
|
|
9182
11145
|
},
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9186
|
-
|
|
9187
|
-
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
|
|
9191
|
-
|
|
9192
|
-
|
|
9193
|
-
|
|
9194
|
-
|
|
9195
|
-
|
|
9196
|
-
|
|
9197
|
-
|
|
9198
|
-
|
|
11146
|
+
{
|
|
11147
|
+
type: ToolTypes.Function,
|
|
11148
|
+
function: {
|
|
11149
|
+
name: BrowserToolNames.SCROLL,
|
|
11150
|
+
description: "Scroll the page in a given direction.",
|
|
11151
|
+
parameters: {
|
|
11152
|
+
type: "object",
|
|
11153
|
+
properties: {
|
|
11154
|
+
direction: {
|
|
11155
|
+
type: "string",
|
|
11156
|
+
enum: ["up", "down"],
|
|
11157
|
+
description: "Scroll direction"
|
|
11158
|
+
},
|
|
11159
|
+
amount: {
|
|
11160
|
+
type: "string",
|
|
11161
|
+
enum: ["page", "half", "toTop", "toBottom"],
|
|
11162
|
+
description: "How much to scroll. Default: page"
|
|
11163
|
+
}
|
|
11164
|
+
},
|
|
11165
|
+
required: ["direction"]
|
|
11166
|
+
}
|
|
9199
11167
|
}
|
|
9200
11168
|
},
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
|
|
11169
|
+
{
|
|
11170
|
+
type: ToolTypes.Function,
|
|
11171
|
+
function: {
|
|
11172
|
+
name: BrowserToolNames.SCREENSHOT,
|
|
11173
|
+
description: "Capture a PNG screenshot of the current page. Use sparingly - prefer accessibility tree for decisions.",
|
|
11174
|
+
parameters: {
|
|
11175
|
+
type: "object",
|
|
11176
|
+
properties: {
|
|
11177
|
+
fullPage: {
|
|
11178
|
+
type: "boolean",
|
|
11179
|
+
description: "Capture full scrollable page. Default: false (viewport only)"
|
|
11180
|
+
}
|
|
11181
|
+
}
|
|
11182
|
+
}
|
|
11183
|
+
}
|
|
9213
11184
|
},
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
|
|
9218
|
-
|
|
9219
|
-
|
|
11185
|
+
{
|
|
11186
|
+
type: ToolTypes.Function,
|
|
11187
|
+
function: {
|
|
11188
|
+
name: BrowserToolNames.EXTRACT,
|
|
11189
|
+
description: "Extract structured data from the page using CSS selectors.",
|
|
11190
|
+
parameters: {
|
|
11191
|
+
type: "object",
|
|
11192
|
+
properties: {
|
|
11193
|
+
selector: {
|
|
11194
|
+
type: "string",
|
|
11195
|
+
description: "CSS selector for elements to extract"
|
|
11196
|
+
},
|
|
11197
|
+
attribute: {
|
|
11198
|
+
type: "string",
|
|
11199
|
+
description: "Attribute to extract (textContent, href, src, etc.). Default: textContent"
|
|
11200
|
+
},
|
|
11201
|
+
multiple: {
|
|
11202
|
+
type: "boolean",
|
|
11203
|
+
description: "Return all matches as JSON array. Default: false (first match only)"
|
|
11204
|
+
}
|
|
11205
|
+
},
|
|
11206
|
+
required: ["selector"]
|
|
11207
|
+
}
|
|
11208
|
+
}
|
|
11209
|
+
}
|
|
11210
|
+
];
|
|
11211
|
+
}
|
|
11212
|
+
/**
|
|
11213
|
+
* Register tool handlers into an existing registry.
|
|
11214
|
+
*/
|
|
11215
|
+
registerInto(registry) {
|
|
11216
|
+
registry.register(BrowserToolNames.NAVIGATE, this.navigate.bind(this));
|
|
11217
|
+
registry.register(BrowserToolNames.CLICK, this.click.bind(this));
|
|
11218
|
+
registry.register(BrowserToolNames.TYPE, this.type.bind(this));
|
|
11219
|
+
registry.register(BrowserToolNames.SNAPSHOT, this.snapshot.bind(this));
|
|
11220
|
+
registry.register(BrowserToolNames.SCROLL, this.scroll.bind(this));
|
|
11221
|
+
registry.register(BrowserToolNames.SCREENSHOT, this.screenshot.bind(this));
|
|
11222
|
+
registry.register(BrowserToolNames.EXTRACT, this.extract.bind(this));
|
|
11223
|
+
return registry;
|
|
11224
|
+
}
|
|
11225
|
+
/**
|
|
11226
|
+
* Create a new registry with just this pack's tools.
|
|
11227
|
+
*/
|
|
11228
|
+
toRegistry() {
|
|
11229
|
+
return this.registerInto(new ToolRegistry());
|
|
11230
|
+
}
|
|
11231
|
+
// ========================================================================
|
|
11232
|
+
// Private: Helpers
|
|
11233
|
+
// ========================================================================
|
|
11234
|
+
ensureInitialized() {
|
|
11235
|
+
if (!this.page) {
|
|
11236
|
+
throw new Error(
|
|
11237
|
+
"BrowserToolPack not initialized. Call initialize() first."
|
|
11238
|
+
);
|
|
11239
|
+
}
|
|
11240
|
+
}
|
|
11241
|
+
parseArgs(call, required) {
|
|
11242
|
+
const func = call.function;
|
|
11243
|
+
if (!func) {
|
|
11244
|
+
throw new ToolArgumentError({
|
|
11245
|
+
message: "tool call missing function",
|
|
11246
|
+
toolCallId: call.id,
|
|
11247
|
+
toolName: "",
|
|
11248
|
+
rawArguments: ""
|
|
11249
|
+
});
|
|
11250
|
+
}
|
|
11251
|
+
const rawArgs = func.arguments || "{}";
|
|
11252
|
+
let parsed;
|
|
11253
|
+
try {
|
|
11254
|
+
parsed = JSON.parse(rawArgs);
|
|
11255
|
+
} catch (err) {
|
|
11256
|
+
throw new ToolArgumentError({
|
|
11257
|
+
message: `invalid JSON arguments: ${err.message}`,
|
|
11258
|
+
toolCallId: call.id,
|
|
11259
|
+
toolName: func.name,
|
|
11260
|
+
rawArguments: rawArgs
|
|
11261
|
+
});
|
|
11262
|
+
}
|
|
11263
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
11264
|
+
throw new ToolArgumentError({
|
|
11265
|
+
message: "arguments must be an object",
|
|
11266
|
+
toolCallId: call.id,
|
|
11267
|
+
toolName: func.name,
|
|
11268
|
+
rawArguments: rawArgs
|
|
11269
|
+
});
|
|
11270
|
+
}
|
|
11271
|
+
const args = parsed;
|
|
11272
|
+
for (const key of required) {
|
|
11273
|
+
const value = args[key];
|
|
11274
|
+
if (value === void 0 || value === null || value === "") {
|
|
11275
|
+
throw new ToolArgumentError({
|
|
11276
|
+
message: `${key} is required`,
|
|
11277
|
+
toolCallId: call.id,
|
|
11278
|
+
toolName: func.name,
|
|
11279
|
+
rawArguments: rawArgs
|
|
11280
|
+
});
|
|
11281
|
+
}
|
|
11282
|
+
}
|
|
11283
|
+
return args;
|
|
11284
|
+
}
|
|
11285
|
+
validateUrl(url, call) {
|
|
11286
|
+
let parsed;
|
|
11287
|
+
try {
|
|
11288
|
+
parsed = new URL(url);
|
|
11289
|
+
} catch {
|
|
11290
|
+
throw new ToolArgumentError({
|
|
11291
|
+
message: `Invalid URL: ${url}`,
|
|
11292
|
+
toolCallId: call.id,
|
|
11293
|
+
toolName: call.function?.name ?? "",
|
|
11294
|
+
rawArguments: call.function?.arguments ?? ""
|
|
11295
|
+
});
|
|
11296
|
+
}
|
|
11297
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
11298
|
+
throw new ToolArgumentError({
|
|
11299
|
+
message: `Invalid protocol: ${parsed.protocol}. Only http/https allowed.`,
|
|
11300
|
+
toolCallId: call.id,
|
|
11301
|
+
toolName: call.function?.name ?? "",
|
|
11302
|
+
rawArguments: call.function?.arguments ?? ""
|
|
11303
|
+
});
|
|
11304
|
+
}
|
|
11305
|
+
const domain = parsed.hostname;
|
|
11306
|
+
if (this.cfg.blockedDomains.some((d) => domain.endsWith(d))) {
|
|
11307
|
+
throw new ToolArgumentError({
|
|
11308
|
+
message: `Domain blocked: ${domain}`,
|
|
11309
|
+
toolCallId: call.id,
|
|
11310
|
+
toolName: call.function?.name ?? "",
|
|
11311
|
+
rawArguments: call.function?.arguments ?? ""
|
|
11312
|
+
});
|
|
11313
|
+
}
|
|
11314
|
+
if (this.cfg.allowedDomains.length > 0) {
|
|
11315
|
+
if (!this.cfg.allowedDomains.some((d) => domain.endsWith(d))) {
|
|
11316
|
+
throw new ToolArgumentError({
|
|
11317
|
+
message: `Domain not in allowlist: ${domain}`,
|
|
11318
|
+
toolCallId: call.id,
|
|
11319
|
+
toolName: call.function?.name ?? "",
|
|
11320
|
+
rawArguments: call.function?.arguments ?? ""
|
|
11321
|
+
});
|
|
11322
|
+
}
|
|
11323
|
+
}
|
|
11324
|
+
}
|
|
11325
|
+
/**
|
|
11326
|
+
* Validates the current page URL against allowlist/blocklist.
|
|
11327
|
+
* Called after navigation and before any action to catch redirects
|
|
11328
|
+
* and in-session navigation to blocked domains.
|
|
11329
|
+
*/
|
|
11330
|
+
ensureCurrentUrlAllowed() {
|
|
11331
|
+
if (!this.page) return;
|
|
11332
|
+
const currentUrl = this.page.url();
|
|
11333
|
+
if (currentUrl === "about:blank") return;
|
|
11334
|
+
let parsed;
|
|
11335
|
+
try {
|
|
11336
|
+
parsed = new URL(currentUrl);
|
|
11337
|
+
} catch {
|
|
11338
|
+
throw new Error(`Current page has invalid URL: ${currentUrl}`);
|
|
11339
|
+
}
|
|
11340
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
11341
|
+
throw new Error(
|
|
11342
|
+
`Current page protocol not allowed: ${parsed.protocol}. Only http/https allowed.`
|
|
11343
|
+
);
|
|
11344
|
+
}
|
|
11345
|
+
const domain = parsed.hostname;
|
|
11346
|
+
if (this.cfg.blockedDomains.some((d) => domain.endsWith(d))) {
|
|
11347
|
+
throw new Error(`Current page domain is blocked: ${domain}`);
|
|
11348
|
+
}
|
|
11349
|
+
if (this.cfg.allowedDomains.length > 0) {
|
|
11350
|
+
if (!this.cfg.allowedDomains.some((d) => domain.endsWith(d))) {
|
|
11351
|
+
throw new Error(`Current page domain not in allowlist: ${domain}`);
|
|
11352
|
+
}
|
|
11353
|
+
}
|
|
11354
|
+
}
|
|
11355
|
+
async getAccessibilityTree() {
|
|
11356
|
+
this.ensureInitialized();
|
|
11357
|
+
if (!this.cdpSession) {
|
|
11358
|
+
this.cdpSession = await this.page.context().newCDPSession(this.page);
|
|
11359
|
+
await this.cdpSession.send("Accessibility.enable");
|
|
11360
|
+
}
|
|
11361
|
+
const response = await this.cdpSession.send(
|
|
11362
|
+
"Accessibility.getFullAXTree"
|
|
11363
|
+
);
|
|
11364
|
+
return response.nodes;
|
|
11365
|
+
}
|
|
11366
|
+
formatAXTree(nodes) {
|
|
11367
|
+
const lines = [];
|
|
11368
|
+
let count = 0;
|
|
11369
|
+
for (const node of nodes) {
|
|
11370
|
+
if (count >= this.cfg.maxSnapshotNodes) {
|
|
11371
|
+
lines.push(`[truncated at ${this.cfg.maxSnapshotNodes} nodes]`);
|
|
11372
|
+
break;
|
|
11373
|
+
}
|
|
11374
|
+
if (node.ignored) {
|
|
11375
|
+
continue;
|
|
11376
|
+
}
|
|
11377
|
+
const role = node.role?.value || "unknown";
|
|
11378
|
+
const name = node.name?.value || "";
|
|
11379
|
+
if (!name && ["generic", "none", "text"].includes(role)) {
|
|
11380
|
+
continue;
|
|
11381
|
+
}
|
|
11382
|
+
const states = [];
|
|
11383
|
+
if (node.properties) {
|
|
11384
|
+
for (const prop of node.properties) {
|
|
11385
|
+
if (prop.value?.value === true) {
|
|
11386
|
+
const stateName = prop.name;
|
|
11387
|
+
if (["focused", "checked", "disabled", "expanded", "selected"].includes(
|
|
11388
|
+
stateName
|
|
11389
|
+
)) {
|
|
11390
|
+
states.push(stateName);
|
|
11391
|
+
}
|
|
11392
|
+
}
|
|
11393
|
+
}
|
|
11394
|
+
}
|
|
11395
|
+
const stateStr = states.length ? " " + states.join(" ") : "";
|
|
11396
|
+
const nameStr = name ? ` "${name}"` : "";
|
|
11397
|
+
lines.push(`[${role}${nameStr}${stateStr}]`);
|
|
11398
|
+
count++;
|
|
11399
|
+
}
|
|
11400
|
+
return lines.join("\n");
|
|
11401
|
+
}
|
|
11402
|
+
// ========================================================================
|
|
11403
|
+
// Private: Tool Handlers
|
|
11404
|
+
// ========================================================================
|
|
11405
|
+
async navigate(_args, call) {
|
|
11406
|
+
const args = this.parseArgs(call, ["url"]);
|
|
11407
|
+
this.validateUrl(args.url, call);
|
|
11408
|
+
this.ensureInitialized();
|
|
11409
|
+
const waitUntil = args.waitUntil ?? "domcontentloaded";
|
|
11410
|
+
await this.page.goto(args.url, {
|
|
11411
|
+
timeout: this.cfg.navigationTimeoutMs,
|
|
11412
|
+
waitUntil
|
|
11413
|
+
});
|
|
11414
|
+
this.ensureCurrentUrlAllowed();
|
|
11415
|
+
const tree = await this.getAccessibilityTree();
|
|
11416
|
+
return this.formatAXTree(tree);
|
|
11417
|
+
}
|
|
11418
|
+
async click(_args, call) {
|
|
11419
|
+
const args = this.parseArgs(call, ["name"]);
|
|
11420
|
+
this.ensureInitialized();
|
|
11421
|
+
this.ensureCurrentUrlAllowed();
|
|
11422
|
+
let locator;
|
|
11423
|
+
if (args.role) {
|
|
11424
|
+
locator = this.page.getByRole(args.role, {
|
|
11425
|
+
name: args.name
|
|
11426
|
+
});
|
|
11427
|
+
} else {
|
|
11428
|
+
locator = this.page.getByRole("button", { name: args.name }).or(this.page.getByRole("link", { name: args.name })).or(this.page.getByRole("menuitem", { name: args.name }));
|
|
11429
|
+
}
|
|
11430
|
+
await locator.click({ timeout: this.cfg.actionTimeoutMs });
|
|
11431
|
+
const tree = await this.getAccessibilityTree();
|
|
11432
|
+
return this.formatAXTree(tree);
|
|
11433
|
+
}
|
|
11434
|
+
async type(_args, call) {
|
|
11435
|
+
const args = this.parseArgs(call, ["name", "text"]);
|
|
11436
|
+
this.ensureInitialized();
|
|
11437
|
+
this.ensureCurrentUrlAllowed();
|
|
11438
|
+
const role = args.role ?? "textbox";
|
|
11439
|
+
const locator = this.page.getByRole(role, { name: args.name });
|
|
11440
|
+
await locator.fill(args.text, { timeout: this.cfg.actionTimeoutMs });
|
|
11441
|
+
return `Typed "${args.text}" into ${role} "${args.name}"`;
|
|
11442
|
+
}
|
|
11443
|
+
async snapshot(_args, _call) {
|
|
11444
|
+
this.ensureInitialized();
|
|
11445
|
+
this.ensureCurrentUrlAllowed();
|
|
11446
|
+
const tree = await this.getAccessibilityTree();
|
|
11447
|
+
return this.formatAXTree(tree);
|
|
11448
|
+
}
|
|
11449
|
+
async scroll(_args, call) {
|
|
11450
|
+
const args = this.parseArgs(call, ["direction"]);
|
|
11451
|
+
this.ensureInitialized();
|
|
11452
|
+
this.ensureCurrentUrlAllowed();
|
|
11453
|
+
const amount = args.amount ?? "page";
|
|
11454
|
+
if (amount === "toTop") {
|
|
11455
|
+
await this.page.evaluate(() => window.scrollTo(0, 0));
|
|
11456
|
+
} else if (amount === "toBottom") {
|
|
11457
|
+
await this.page.evaluate(
|
|
11458
|
+
() => window.scrollTo(0, document.body.scrollHeight)
|
|
11459
|
+
);
|
|
11460
|
+
} else {
|
|
11461
|
+
const viewport = this.page.viewportSize();
|
|
11462
|
+
const height = viewport?.height ?? 800;
|
|
11463
|
+
const scrollAmount = amount === "half" ? height / 2 : height;
|
|
11464
|
+
const delta = args.direction === "down" ? scrollAmount : -scrollAmount;
|
|
11465
|
+
await this.page.evaluate((d) => window.scrollBy(0, d), delta);
|
|
11466
|
+
}
|
|
11467
|
+
const tree = await this.getAccessibilityTree();
|
|
11468
|
+
return this.formatAXTree(tree);
|
|
11469
|
+
}
|
|
11470
|
+
async screenshot(_args, call) {
|
|
11471
|
+
const args = this.parseArgs(call, []);
|
|
11472
|
+
this.ensureInitialized();
|
|
11473
|
+
this.ensureCurrentUrlAllowed();
|
|
11474
|
+
const buffer = await this.page.screenshot({
|
|
11475
|
+
fullPage: args.fullPage ?? false,
|
|
11476
|
+
type: "png"
|
|
11477
|
+
});
|
|
11478
|
+
if (buffer.length > BrowserDefaults.MAX_SCREENSHOT_BYTES) {
|
|
11479
|
+
throw new Error(
|
|
11480
|
+
`Screenshot size (${buffer.length} bytes) exceeds maximum allowed (${BrowserDefaults.MAX_SCREENSHOT_BYTES} bytes). Try capturing viewport only.`
|
|
11481
|
+
);
|
|
11482
|
+
}
|
|
11483
|
+
const base64 = buffer.toString("base64");
|
|
11484
|
+
return `data:image/png;base64,${base64}`;
|
|
11485
|
+
}
|
|
11486
|
+
async extract(_args, call) {
|
|
11487
|
+
const args = this.parseArgs(call, ["selector"]);
|
|
11488
|
+
this.ensureInitialized();
|
|
11489
|
+
this.ensureCurrentUrlAllowed();
|
|
11490
|
+
const attribute = args.attribute ?? "textContent";
|
|
11491
|
+
const multiple = args.multiple ?? false;
|
|
11492
|
+
if (multiple) {
|
|
11493
|
+
const elements = this.page.locator(args.selector);
|
|
11494
|
+
const count = await elements.count();
|
|
11495
|
+
const results = [];
|
|
11496
|
+
for (let i = 0; i < count; i++) {
|
|
11497
|
+
const el = elements.nth(i);
|
|
11498
|
+
let value;
|
|
11499
|
+
if (attribute === "textContent") {
|
|
11500
|
+
value = await el.textContent();
|
|
11501
|
+
} else {
|
|
11502
|
+
value = await el.getAttribute(attribute);
|
|
11503
|
+
}
|
|
11504
|
+
if (value !== null) {
|
|
11505
|
+
results.push(value.trim());
|
|
11506
|
+
}
|
|
11507
|
+
}
|
|
11508
|
+
return JSON.stringify(results);
|
|
11509
|
+
} else {
|
|
11510
|
+
const el = this.page.locator(args.selector).first();
|
|
11511
|
+
let value;
|
|
11512
|
+
if (attribute === "textContent") {
|
|
11513
|
+
value = await el.textContent();
|
|
11514
|
+
} else {
|
|
11515
|
+
value = await el.getAttribute(attribute);
|
|
11516
|
+
}
|
|
11517
|
+
return value?.trim() ?? "";
|
|
11518
|
+
}
|
|
11519
|
+
}
|
|
11520
|
+
};
|
|
11521
|
+
function createBrowserToolPack(options = {}) {
|
|
11522
|
+
return new BrowserToolPack(options);
|
|
11523
|
+
}
|
|
11524
|
+
function createBrowserTools(options = {}) {
|
|
11525
|
+
const pack = new BrowserToolPack(options);
|
|
11526
|
+
return { pack, registry: pack.toRegistry() };
|
|
11527
|
+
}
|
|
11528
|
+
|
|
11529
|
+
// src/tools_runner.ts
|
|
11530
|
+
var ToolRunner = class {
|
|
11531
|
+
constructor(options) {
|
|
11532
|
+
this.registry = options.registry;
|
|
11533
|
+
this.runsClient = options.runsClient;
|
|
11534
|
+
this.customerId = options.customerId;
|
|
11535
|
+
this.onBeforeExecute = options.onBeforeExecute;
|
|
11536
|
+
this.onAfterExecute = options.onAfterExecute;
|
|
11537
|
+
this.onSubmitted = options.onSubmitted;
|
|
11538
|
+
this.onError = options.onError;
|
|
11539
|
+
}
|
|
11540
|
+
/**
|
|
11541
|
+
* Handles a node_waiting event by executing tools and submitting results.
|
|
11542
|
+
*
|
|
11543
|
+
* @param runId - The run ID
|
|
11544
|
+
* @param nodeId - The node ID that is waiting
|
|
11545
|
+
* @param waiting - The waiting state with pending tool calls
|
|
11546
|
+
* @returns The submission response with accepted count and new status
|
|
11547
|
+
*
|
|
11548
|
+
* @example
|
|
11549
|
+
* ```typescript
|
|
11550
|
+
* for await (const event of client.runs.events(runId)) {
|
|
11551
|
+
* if (event.type === "node_waiting") {
|
|
11552
|
+
* const result = await runner.handleNodeWaiting(
|
|
11553
|
+
* runId,
|
|
11554
|
+
* event.node_id,
|
|
11555
|
+
* event.waiting
|
|
11556
|
+
* );
|
|
11557
|
+
* console.log(`Submitted ${result.accepted} results, status: ${result.status}`);
|
|
11558
|
+
* }
|
|
11559
|
+
* }
|
|
11560
|
+
* ```
|
|
11561
|
+
*/
|
|
11562
|
+
async handleNodeWaiting(runId, nodeId, waiting) {
|
|
11563
|
+
const results = [];
|
|
11564
|
+
for (const pending of waiting.pending_tool_calls) {
|
|
11565
|
+
try {
|
|
11566
|
+
await this.onBeforeExecute?.(pending);
|
|
11567
|
+
const toolCall = createToolCall(
|
|
11568
|
+
pending.tool_call_id,
|
|
11569
|
+
pending.name,
|
|
11570
|
+
pending.arguments
|
|
11571
|
+
);
|
|
11572
|
+
const result = await this.registry.execute(toolCall);
|
|
11573
|
+
results.push(result);
|
|
11574
|
+
await this.onAfterExecute?.(result);
|
|
11575
|
+
} catch (err) {
|
|
11576
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
11577
|
+
await this.onError?.(error, pending);
|
|
11578
|
+
results.push({
|
|
11579
|
+
toolCallId: pending.tool_call_id,
|
|
11580
|
+
toolName: pending.name,
|
|
11581
|
+
result: null,
|
|
11582
|
+
error: error.message
|
|
11583
|
+
});
|
|
11584
|
+
}
|
|
11585
|
+
}
|
|
11586
|
+
const response = await this.runsClient.submitToolResults(
|
|
11587
|
+
runId,
|
|
11588
|
+
{
|
|
11589
|
+
node_id: nodeId,
|
|
11590
|
+
step: waiting.step,
|
|
11591
|
+
request_id: waiting.request_id,
|
|
11592
|
+
results: results.map((r) => ({
|
|
11593
|
+
tool_call_id: r.toolCallId,
|
|
11594
|
+
name: r.toolName,
|
|
11595
|
+
output: r.error ? `Error: ${r.error}` : typeof r.result === "string" ? r.result : JSON.stringify(r.result)
|
|
11596
|
+
}))
|
|
9220
11597
|
},
|
|
9221
|
-
|
|
9222
|
-
|
|
11598
|
+
{ customerId: this.customerId }
|
|
11599
|
+
);
|
|
11600
|
+
await this.onSubmitted?.(runId, response.accepted, response.status);
|
|
11601
|
+
return {
|
|
11602
|
+
accepted: response.accepted,
|
|
11603
|
+
status: response.status,
|
|
11604
|
+
results
|
|
11605
|
+
};
|
|
11606
|
+
}
|
|
11607
|
+
/**
|
|
11608
|
+
* Processes a stream of run events, automatically handling node_waiting events.
|
|
11609
|
+
*
|
|
11610
|
+
* This is the main entry point for running a workflow with client-side tools.
|
|
11611
|
+
* It yields all events through (including node_waiting after handling).
|
|
11612
|
+
*
|
|
11613
|
+
* @param runId - The run ID to process
|
|
11614
|
+
* @param events - AsyncIterable of run events (from RunsClient.events())
|
|
11615
|
+
* @yields All run events, with node_waiting events handled automatically
|
|
11616
|
+
*
|
|
11617
|
+
* @example
|
|
11618
|
+
* ```typescript
|
|
11619
|
+
* const run = await client.runs.create(workflowSpec);
|
|
11620
|
+
* const eventStream = client.runs.events(run.run_id);
|
|
11621
|
+
*
|
|
11622
|
+
* for await (const event of runner.processEvents(run.run_id, eventStream)) {
|
|
11623
|
+
* switch (event.type) {
|
|
11624
|
+
* case "node_started":
|
|
11625
|
+
* console.log(`Node ${event.node_id} started`);
|
|
11626
|
+
* break;
|
|
11627
|
+
* case "node_succeeded":
|
|
11628
|
+
* console.log(`Node ${event.node_id} succeeded`);
|
|
11629
|
+
* break;
|
|
11630
|
+
* case "run_succeeded":
|
|
11631
|
+
* console.log("Run completed!");
|
|
11632
|
+
* break;
|
|
11633
|
+
* }
|
|
11634
|
+
* }
|
|
11635
|
+
* ```
|
|
11636
|
+
*/
|
|
11637
|
+
async *processEvents(runId, events) {
|
|
11638
|
+
for await (const event of events) {
|
|
11639
|
+
if (event.type === "node_waiting") {
|
|
11640
|
+
const waitingEvent = event;
|
|
11641
|
+
try {
|
|
11642
|
+
await this.handleNodeWaiting(
|
|
11643
|
+
runId,
|
|
11644
|
+
waitingEvent.node_id,
|
|
11645
|
+
waitingEvent.waiting
|
|
11646
|
+
);
|
|
11647
|
+
} catch (err) {
|
|
11648
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
11649
|
+
await this.onError?.(error);
|
|
11650
|
+
throw error;
|
|
11651
|
+
}
|
|
11652
|
+
}
|
|
11653
|
+
yield event;
|
|
9223
11654
|
}
|
|
9224
|
-
}
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
9230
|
-
|
|
9231
|
-
|
|
11655
|
+
}
|
|
11656
|
+
/**
|
|
11657
|
+
* Checks if a run event is a node_waiting event.
|
|
11658
|
+
* Utility for filtering events when not using processEvents().
|
|
11659
|
+
*/
|
|
11660
|
+
static isNodeWaiting(event) {
|
|
11661
|
+
return event.type === "node_waiting";
|
|
11662
|
+
}
|
|
11663
|
+
/**
|
|
11664
|
+
* Checks if a run status is terminal (succeeded, failed, or canceled).
|
|
11665
|
+
* Utility for determining when to stop polling.
|
|
11666
|
+
*/
|
|
11667
|
+
static isTerminalStatus(status) {
|
|
11668
|
+
return status === "succeeded" || status === "failed" || status === "canceled";
|
|
11669
|
+
}
|
|
9232
11670
|
};
|
|
11671
|
+
function createToolRunner(options) {
|
|
11672
|
+
return new ToolRunner(options);
|
|
11673
|
+
}
|
|
9233
11674
|
|
|
9234
11675
|
// src/generated/index.ts
|
|
9235
11676
|
var generated_exports = {};
|
|
@@ -9286,9 +11727,11 @@ var ModelRelay = class _ModelRelay {
|
|
|
9286
11727
|
metrics: cfg.metrics,
|
|
9287
11728
|
trace: cfg.trace
|
|
9288
11729
|
});
|
|
11730
|
+
this.images = new ImagesClient(http, auth);
|
|
9289
11731
|
this.customers = new CustomersClient(http, { apiKey, accessToken, tokenProvider });
|
|
9290
11732
|
this.tiers = new TiersClient(http, { apiKey });
|
|
9291
11733
|
this.models = new ModelsClient(http);
|
|
11734
|
+
this.sessions = new SessionsClient(this, http, auth);
|
|
9292
11735
|
}
|
|
9293
11736
|
forCustomer(customerId) {
|
|
9294
11737
|
return new CustomerScopedModelRelay(this.responses, customerId, this.baseUrl);
|
|
@@ -9302,6 +11745,10 @@ function resolveBaseUrl(override) {
|
|
|
9302
11745
|
0 && (module.exports = {
|
|
9303
11746
|
APIError,
|
|
9304
11747
|
AuthClient,
|
|
11748
|
+
BillingProviders,
|
|
11749
|
+
BrowserDefaults,
|
|
11750
|
+
BrowserToolNames,
|
|
11751
|
+
BrowserToolPack,
|
|
9305
11752
|
ConfigError,
|
|
9306
11753
|
ContentPartTypes,
|
|
9307
11754
|
CustomerResponsesClient,
|
|
@@ -9311,10 +11758,17 @@ function resolveBaseUrl(override) {
|
|
|
9311
11758
|
DEFAULT_BASE_URL,
|
|
9312
11759
|
DEFAULT_CLIENT_HEADER,
|
|
9313
11760
|
DEFAULT_CONNECT_TIMEOUT_MS,
|
|
11761
|
+
DEFAULT_IGNORE_DIRS,
|
|
9314
11762
|
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
9315
11763
|
ErrorCodes,
|
|
11764
|
+
FSDefaults,
|
|
11765
|
+
FSToolNames,
|
|
9316
11766
|
FrontendTokenProvider,
|
|
11767
|
+
ImagesClient,
|
|
9317
11768
|
InputItemTypes,
|
|
11769
|
+
LocalFSToolPack,
|
|
11770
|
+
LocalSession,
|
|
11771
|
+
MemorySessionStore,
|
|
9318
11772
|
MessageRoles,
|
|
9319
11773
|
ModelRelay,
|
|
9320
11774
|
ModelRelayError,
|
|
@@ -9322,22 +11776,27 @@ function resolveBaseUrl(override) {
|
|
|
9322
11776
|
OIDCExchangeTokenProvider,
|
|
9323
11777
|
OutputFormatTypes,
|
|
9324
11778
|
OutputItemTypes,
|
|
11779
|
+
PathEscapeError,
|
|
9325
11780
|
ResponsesClient,
|
|
9326
11781
|
ResponsesStream,
|
|
9327
11782
|
RunsClient,
|
|
9328
11783
|
RunsEventStream,
|
|
9329
11784
|
SDK_VERSION,
|
|
11785
|
+
SessionsClient,
|
|
9330
11786
|
StopReasons,
|
|
9331
11787
|
StreamProtocolError,
|
|
9332
11788
|
StreamTimeoutError,
|
|
9333
11789
|
StructuredDecodeError,
|
|
9334
11790
|
StructuredExhaustedError,
|
|
9335
11791
|
StructuredJSONStream,
|
|
11792
|
+
SubscriptionStatuses,
|
|
9336
11793
|
TiersClient,
|
|
9337
11794
|
ToolArgsError,
|
|
11795
|
+
ToolArgumentError,
|
|
9338
11796
|
ToolCallAccumulator,
|
|
9339
11797
|
ToolChoiceTypes,
|
|
9340
11798
|
ToolRegistry,
|
|
11799
|
+
ToolRunner,
|
|
9341
11800
|
ToolTypes,
|
|
9342
11801
|
TransportError,
|
|
9343
11802
|
WORKFLOWS_COMPILE_PATH,
|
|
@@ -9349,17 +11808,25 @@ function resolveBaseUrl(override) {
|
|
|
9349
11808
|
WorkflowsClient,
|
|
9350
11809
|
asModelId,
|
|
9351
11810
|
asProviderId,
|
|
11811
|
+
asSessionId,
|
|
9352
11812
|
asTierCode,
|
|
9353
11813
|
assistantMessageWithToolCalls,
|
|
9354
11814
|
createAccessTokenAuth,
|
|
9355
11815
|
createApiKeyAuth,
|
|
9356
11816
|
createAssistantMessage,
|
|
11817
|
+
createBrowserToolPack,
|
|
11818
|
+
createBrowserTools,
|
|
9357
11819
|
createFunctionCall,
|
|
9358
11820
|
createFunctionTool,
|
|
9359
11821
|
createFunctionToolFromSchema,
|
|
11822
|
+
createLocalFSToolPack,
|
|
11823
|
+
createLocalFSTools,
|
|
11824
|
+
createLocalSession,
|
|
11825
|
+
createMemorySessionStore,
|
|
9360
11826
|
createRetryMessages,
|
|
9361
11827
|
createSystemMessage,
|
|
9362
11828
|
createToolCall,
|
|
11829
|
+
createToolRunner,
|
|
9363
11830
|
createUsage,
|
|
9364
11831
|
createUserMessage,
|
|
9365
11832
|
createWebTool,
|
|
@@ -9367,6 +11834,7 @@ function resolveBaseUrl(override) {
|
|
|
9367
11834
|
executeWithRetry,
|
|
9368
11835
|
firstToolCall,
|
|
9369
11836
|
formatToolErrorForModel,
|
|
11837
|
+
generateSessionId,
|
|
9370
11838
|
generated,
|
|
9371
11839
|
getRetryableErrors,
|
|
9372
11840
|
hasRetryableErrors,
|