@uncensoredcode/openbridge 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -0
- package/bin/openbridge.js +10 -0
- package/package.json +85 -0
- package/packages/cli/dist/args.d.ts +30 -0
- package/packages/cli/dist/args.js +160 -0
- package/packages/cli/dist/cli.d.ts +2 -0
- package/packages/cli/dist/cli.js +9 -0
- package/packages/cli/dist/index.d.ts +26 -0
- package/packages/cli/dist/index.js +76 -0
- package/packages/runtime/dist/assistant-protocol.d.ts +34 -0
- package/packages/runtime/dist/assistant-protocol.js +121 -0
- package/packages/runtime/dist/execution/in-process.d.ts +14 -0
- package/packages/runtime/dist/execution/in-process.js +45 -0
- package/packages/runtime/dist/execution/types.d.ts +49 -0
- package/packages/runtime/dist/execution/types.js +20 -0
- package/packages/runtime/dist/index.d.ts +86 -0
- package/packages/runtime/dist/index.js +60 -0
- package/packages/runtime/dist/normalizers/index.d.ts +6 -0
- package/packages/runtime/dist/normalizers/index.js +12 -0
- package/packages/runtime/dist/normalizers/legacy-packet.d.ts +6 -0
- package/packages/runtime/dist/normalizers/legacy-packet.js +131 -0
- package/packages/runtime/dist/output-sanitizer.d.ts +23 -0
- package/packages/runtime/dist/output-sanitizer.js +78 -0
- package/packages/runtime/dist/packet-extractor.d.ts +17 -0
- package/packages/runtime/dist/packet-extractor.js +43 -0
- package/packages/runtime/dist/packet-normalizer.d.ts +21 -0
- package/packages/runtime/dist/packet-normalizer.js +47 -0
- package/packages/runtime/dist/prompt-compiler.d.ts +28 -0
- package/packages/runtime/dist/prompt-compiler.js +301 -0
- package/packages/runtime/dist/protocol.d.ts +44 -0
- package/packages/runtime/dist/protocol.js +165 -0
- package/packages/runtime/dist/provider-failure.d.ts +52 -0
- package/packages/runtime/dist/provider-failure.js +236 -0
- package/packages/runtime/dist/provider.d.ts +40 -0
- package/packages/runtime/dist/provider.js +1 -0
- package/packages/runtime/dist/runtime.d.ts +86 -0
- package/packages/runtime/dist/runtime.js +462 -0
- package/packages/runtime/dist/session-bound-provider.d.ts +52 -0
- package/packages/runtime/dist/session-bound-provider.js +366 -0
- package/packages/runtime/dist/tool-name-aliases.d.ts +5 -0
- package/packages/runtime/dist/tool-name-aliases.js +13 -0
- package/packages/runtime/dist/tools/bash.d.ts +9 -0
- package/packages/runtime/dist/tools/bash.js +157 -0
- package/packages/runtime/dist/tools/edit.d.ts +9 -0
- package/packages/runtime/dist/tools/edit.js +94 -0
- package/packages/runtime/dist/tools/index.d.ts +39 -0
- package/packages/runtime/dist/tools/index.js +27 -0
- package/packages/runtime/dist/tools/list-dir.d.ts +9 -0
- package/packages/runtime/dist/tools/list-dir.js +127 -0
- package/packages/runtime/dist/tools/read.d.ts +9 -0
- package/packages/runtime/dist/tools/read.js +56 -0
- package/packages/runtime/dist/tools/registry.d.ts +15 -0
- package/packages/runtime/dist/tools/registry.js +38 -0
- package/packages/runtime/dist/tools/runtime-path.d.ts +7 -0
- package/packages/runtime/dist/tools/runtime-path.js +22 -0
- package/packages/runtime/dist/tools/search-files.d.ts +9 -0
- package/packages/runtime/dist/tools/search-files.js +149 -0
- package/packages/runtime/dist/tools/text-file.d.ts +32 -0
- package/packages/runtime/dist/tools/text-file.js +101 -0
- package/packages/runtime/dist/tools/workspace-path.d.ts +17 -0
- package/packages/runtime/dist/tools/workspace-path.js +70 -0
- package/packages/runtime/dist/tools/write.d.ts +9 -0
- package/packages/runtime/dist/tools/write.js +59 -0
- package/packages/server/dist/bridge/bridge-model-catalog.d.ts +56 -0
- package/packages/server/dist/bridge/bridge-model-catalog.js +100 -0
- package/packages/server/dist/bridge/bridge-runtime-service.d.ts +61 -0
- package/packages/server/dist/bridge/bridge-runtime-service.js +1386 -0
- package/packages/server/dist/bridge/chat-completions/chat-completion-service.d.ts +127 -0
- package/packages/server/dist/bridge/chat-completions/chat-completion-service.js +1026 -0
- package/packages/server/dist/bridge/index.d.ts +335 -0
- package/packages/server/dist/bridge/index.js +45 -0
- package/packages/server/dist/bridge/live-provider-extraction-canary.d.ts +69 -0
- package/packages/server/dist/bridge/live-provider-extraction-canary.js +186 -0
- package/packages/server/dist/bridge/providers/generic-provider-transport.d.ts +53 -0
- package/packages/server/dist/bridge/providers/generic-provider-transport.js +973 -0
- package/packages/server/dist/bridge/providers/provider-session-resolver.d.ts +17 -0
- package/packages/server/dist/bridge/providers/provider-session-resolver.js +95 -0
- package/packages/server/dist/bridge/providers/provider-streams.d.ts +80 -0
- package/packages/server/dist/bridge/providers/provider-streams.js +844 -0
- package/packages/server/dist/bridge/providers/provider-transport-profile.d.ts +194 -0
- package/packages/server/dist/bridge/providers/provider-transport-profile.js +198 -0
- package/packages/server/dist/bridge/providers/web-provider-transport.d.ts +30 -0
- package/packages/server/dist/bridge/providers/web-provider-transport.js +151 -0
- package/packages/server/dist/bridge/state/file-bridge-state-store.d.ts +36 -0
- package/packages/server/dist/bridge/state/file-bridge-state-store.js +164 -0
- package/packages/server/dist/bridge/stores/local-session-package-store.d.ts +23 -0
- package/packages/server/dist/bridge/stores/local-session-package-store.js +548 -0
- package/packages/server/dist/bridge/stores/provider-store.d.ts +94 -0
- package/packages/server/dist/bridge/stores/provider-store.js +143 -0
- package/packages/server/dist/bridge/stores/session-backed-provider-store.d.ts +7 -0
- package/packages/server/dist/bridge/stores/session-backed-provider-store.js +26 -0
- package/packages/server/dist/bridge/stores/session-package-store.d.ts +286 -0
- package/packages/server/dist/bridge/stores/session-package-store.js +1527 -0
- package/packages/server/dist/bridge/stores/session-store.d.ts +120 -0
- package/packages/server/dist/bridge/stores/session-store.js +139 -0
- package/packages/server/dist/cli/index.d.ts +9 -0
- package/packages/server/dist/cli/index.js +6 -0
- package/packages/server/dist/cli/main.d.ts +2 -0
- package/packages/server/dist/cli/main.js +9 -0
- package/packages/server/dist/cli/run-bridge-server-cli.d.ts +54 -0
- package/packages/server/dist/cli/run-bridge-server-cli.js +371 -0
- package/packages/server/dist/client/bridge-api-client.d.ts +61 -0
- package/packages/server/dist/client/bridge-api-client.js +267 -0
- package/packages/server/dist/client/index.d.ts +11 -0
- package/packages/server/dist/client/index.js +11 -0
- package/packages/server/dist/config/bridge-server-config.d.ts +52 -0
- package/packages/server/dist/config/bridge-server-config.js +118 -0
- package/packages/server/dist/config/index.d.ts +20 -0
- package/packages/server/dist/config/index.js +8 -0
- package/packages/server/dist/http/bridge-api-route-context.d.ts +14 -0
- package/packages/server/dist/http/bridge-api-route-context.js +1 -0
- package/packages/server/dist/http/create-bridge-api-server.d.ts +72 -0
- package/packages/server/dist/http/create-bridge-api-server.js +225 -0
- package/packages/server/dist/http/index.d.ts +5 -0
- package/packages/server/dist/http/index.js +5 -0
- package/packages/server/dist/http/parse-request.d.ts +6 -0
- package/packages/server/dist/http/parse-request.js +27 -0
- package/packages/server/dist/http/register-bridge-api-routes.d.ts +7 -0
- package/packages/server/dist/http/register-bridge-api-routes.js +17 -0
- package/packages/server/dist/http/routes/admin-routes.d.ts +7 -0
- package/packages/server/dist/http/routes/admin-routes.js +135 -0
- package/packages/server/dist/http/routes/chat-completions-route.d.ts +7 -0
- package/packages/server/dist/http/routes/chat-completions-route.js +49 -0
- package/packages/server/dist/http/routes/health-routes.d.ts +6 -0
- package/packages/server/dist/http/routes/health-routes.js +7 -0
- package/packages/server/dist/http/routes/message-routes.d.ts +7 -0
- package/packages/server/dist/http/routes/message-routes.js +7 -0
- package/packages/server/dist/index.d.ts +85 -0
- package/packages/server/dist/index.js +28 -0
- package/packages/server/dist/security/bridge-auth.d.ts +9 -0
- package/packages/server/dist/security/bridge-auth.js +41 -0
- package/packages/server/dist/security/cors-policy.d.ts +5 -0
- package/packages/server/dist/security/cors-policy.js +34 -0
- package/packages/server/dist/security/index.d.ts +16 -0
- package/packages/server/dist/security/index.js +12 -0
- package/packages/server/dist/security/redact-sensitive-values.d.ts +19 -0
- package/packages/server/dist/security/redact-sensitive-values.js +67 -0
- package/packages/server/dist/shared/api-schema.d.ts +133 -0
- package/packages/server/dist/shared/api-schema.js +1 -0
- package/packages/server/dist/shared/bridge-api-error.d.ts +17 -0
- package/packages/server/dist/shared/bridge-api-error.js +19 -0
- package/packages/server/dist/shared/index.d.ts +7 -0
- package/packages/server/dist/shared/index.js +7 -0
- package/packages/server/dist/shared/output.d.ts +5 -0
- package/packages/server/dist/shared/output.js +14 -0
|
@@ -0,0 +1,973 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { createHmac, randomUUID } from "node:crypto";
|
|
3
|
+
import { bridgeRuntime } from "@uncensoredcode/openbridge/runtime";
|
|
4
|
+
import { redactSensitiveValuesModule } from "../../security/redact-sensitive-values.js";
|
|
5
|
+
import { outputModule } from "../../shared/output.js";
|
|
6
|
+
import { providerStreamsModule } from "./provider-streams.js";
|
|
7
|
+
import { providerTransportProfileModule } from "./provider-transport-profile.js";
|
|
8
|
+
const { ProviderFailure } = bridgeRuntime;
|
|
9
|
+
const { sanitizeSensitiveText } = redactSensitiveValuesModule;
|
|
10
|
+
const { sanitizeBridgeApiOutput } = outputModule;
|
|
11
|
+
const { collectConnectJsonCompletion, collectSseCompletion, createSseJsonEventParser, normalizeLeadingAssistantBlock, streamConnectJsonFragments, streamSseFragments } = providerStreamsModule;
|
|
12
|
+
const { selectProviderPrompt } = providerTransportProfileModule;
|
|
13
|
+
const OMIT_TEMPLATE_VALUE = Symbol("omit_template_value");
|
|
14
|
+
async function collectGenericProviderCompletion(input) {
|
|
15
|
+
const prompt = selectProviderPrompt(input.request.messages, input.profile.prompt.mode);
|
|
16
|
+
const binding = await ensureConversationBinding(input.request, input.profile, input.session, prompt);
|
|
17
|
+
const renderContext = ensureDynamicTemplateState({
|
|
18
|
+
request: input.request,
|
|
19
|
+
prompt,
|
|
20
|
+
conversationId: binding.conversationId,
|
|
21
|
+
parentId: binding.parentId
|
|
22
|
+
});
|
|
23
|
+
const preparedHeaders = await resolvePreparedRequestHeaders(input.profile, input.session, renderContext);
|
|
24
|
+
const response = await sendConfiguredRequest(input.profile.request, input.profile, input.session, renderContext, preparedHeaders);
|
|
25
|
+
const completion = input.profile.family === "http-sse"
|
|
26
|
+
? await collectSseCompletion(response.body, createSseJsonEventParser({
|
|
27
|
+
contentPaths: input.profile.response.contentPaths,
|
|
28
|
+
responseIdPaths: input.profile.response.responseIdPaths,
|
|
29
|
+
conversationIdPaths: input.profile.response.conversationIdPaths,
|
|
30
|
+
eventFilters: input.profile.response.eventFilters
|
|
31
|
+
}), input.profile.response.trimLeadingAssistantBlock
|
|
32
|
+
? normalizeLeadingAssistantBlock
|
|
33
|
+
: undefined)
|
|
34
|
+
: input.profile.family === "http-connect"
|
|
35
|
+
? await collectConnectJsonCompletion(response.body, {
|
|
36
|
+
contentPaths: input.profile.response.contentPaths,
|
|
37
|
+
responseIdPaths: input.profile.response.responseIdPaths,
|
|
38
|
+
conversationIdPaths: input.profile.response.conversationIdPaths,
|
|
39
|
+
eventFilters: input.profile.response.eventFilters
|
|
40
|
+
}, input.profile.response.trimLeadingAssistantBlock
|
|
41
|
+
? normalizeLeadingAssistantBlock
|
|
42
|
+
: undefined)
|
|
43
|
+
: await collectJsonCompletion(response, input.profile, input.request.providerId);
|
|
44
|
+
const completionWithFallback = applyConfiguredResponseIdFallback(completion, input.profile, {
|
|
45
|
+
...renderContext
|
|
46
|
+
});
|
|
47
|
+
const normalizedCompletion = input.profile.response.allowVisibleTextFinal
|
|
48
|
+
? {
|
|
49
|
+
...completionWithFallback,
|
|
50
|
+
content: wrapVisibleTextAsFinalPacket(completionWithFallback.content)
|
|
51
|
+
}
|
|
52
|
+
: completionWithFallback;
|
|
53
|
+
return {
|
|
54
|
+
providerId: input.request.providerId,
|
|
55
|
+
modelId: input.request.modelId,
|
|
56
|
+
prompt,
|
|
57
|
+
conversationId: binding.conversationId,
|
|
58
|
+
completion: normalizedCompletion,
|
|
59
|
+
upstreamBinding: resolveNextBinding(binding, normalizedCompletion.responseId, normalizedCompletion.conversationId, input.request.upstreamBinding)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async function openGenericProviderStream(input) {
|
|
63
|
+
const prompt = selectProviderPrompt(input.request.messages, input.profile.prompt.mode);
|
|
64
|
+
const binding = await ensureConversationBinding(input.request, input.profile, input.session, prompt);
|
|
65
|
+
const renderContext = ensureDynamicTemplateState({
|
|
66
|
+
request: input.request,
|
|
67
|
+
prompt,
|
|
68
|
+
conversationId: binding.conversationId,
|
|
69
|
+
parentId: binding.parentId
|
|
70
|
+
});
|
|
71
|
+
const preparedHeaders = await resolvePreparedRequestHeaders(input.profile, input.session, renderContext);
|
|
72
|
+
const response = await sendConfiguredRequest(input.profile.request, input.profile, input.session, renderContext, preparedHeaders);
|
|
73
|
+
if (input.profile.family === "http-json") {
|
|
74
|
+
const completion = applyConfiguredResponseIdFallback(await collectJsonCompletion(response, input.profile, input.request.providerId), input.profile, renderContext);
|
|
75
|
+
return {
|
|
76
|
+
content: singleFragmentStream(completion.content, completion.responseId, completion.conversationId),
|
|
77
|
+
upstreamBinding: Promise.resolve(resolveNextBinding(binding, completion.responseId, completion.conversationId, input.request.upstreamBinding))
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (input.profile.family === "http-connect") {
|
|
81
|
+
let resolveBinding = () => { };
|
|
82
|
+
const bindingPromise = new Promise((resolve) => {
|
|
83
|
+
resolveBinding = resolve;
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
content: maybeWrapVisibleTextFinalStream(streamConnectJsonFragments(response.body, {
|
|
87
|
+
contentPaths: input.profile.response.contentPaths,
|
|
88
|
+
responseIdPaths: input.profile.response.responseIdPaths,
|
|
89
|
+
conversationIdPaths: input.profile.response.conversationIdPaths,
|
|
90
|
+
eventFilters: input.profile.response.eventFilters
|
|
91
|
+
}, (completion) => {
|
|
92
|
+
const completionWithFallback = applyConfiguredResponseIdFallback(completion, input.profile, {
|
|
93
|
+
...renderContext
|
|
94
|
+
});
|
|
95
|
+
const normalized = input.profile.response.trimLeadingAssistantBlock
|
|
96
|
+
? {
|
|
97
|
+
...completionWithFallback,
|
|
98
|
+
content: normalizeLeadingAssistantBlock(completionWithFallback.content)
|
|
99
|
+
}
|
|
100
|
+
: completionWithFallback;
|
|
101
|
+
resolveBinding(resolveNextBinding(binding, normalized.responseId, normalized.conversationId, input.request.upstreamBinding));
|
|
102
|
+
}), input.profile.response.allowVisibleTextFinal),
|
|
103
|
+
upstreamBinding: bindingPromise
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
let resolveBinding = () => { };
|
|
107
|
+
const bindingPromise = new Promise((resolve) => {
|
|
108
|
+
resolveBinding = resolve;
|
|
109
|
+
});
|
|
110
|
+
return {
|
|
111
|
+
content: maybeWrapVisibleTextFinalStream(streamSseFragments(response.body, createSseJsonEventParser({
|
|
112
|
+
contentPaths: input.profile.response.contentPaths,
|
|
113
|
+
responseIdPaths: input.profile.response.responseIdPaths,
|
|
114
|
+
conversationIdPaths: input.profile.response.conversationIdPaths,
|
|
115
|
+
eventFilters: input.profile.response.eventFilters
|
|
116
|
+
}), (completion) => {
|
|
117
|
+
const completionWithFallback = applyConfiguredResponseIdFallback(completion, input.profile, {
|
|
118
|
+
...renderContext
|
|
119
|
+
});
|
|
120
|
+
const normalized = input.profile.response.trimLeadingAssistantBlock
|
|
121
|
+
? {
|
|
122
|
+
...completionWithFallback,
|
|
123
|
+
content: normalizeLeadingAssistantBlock(completionWithFallback.content)
|
|
124
|
+
}
|
|
125
|
+
: completionWithFallback;
|
|
126
|
+
resolveBinding(resolveNextBinding(binding, normalized.responseId, normalized.conversationId, input.request.upstreamBinding));
|
|
127
|
+
}), input.profile.response.allowVisibleTextFinal),
|
|
128
|
+
upstreamBinding: bindingPromise
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function resolveNextBinding(binding, responseId, responseConversationId, fallback) {
|
|
132
|
+
const nextConversationId = binding.conversationId || responseConversationId || fallback?.conversationId || "";
|
|
133
|
+
if (!nextConversationId) {
|
|
134
|
+
return fallback;
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
conversationId: nextConversationId,
|
|
138
|
+
parentId: responseId || binding.parentId || fallback?.parentId || ""
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
async function ensureConversationBinding(request, profile, session, prompt) {
|
|
142
|
+
const existingConversationId = request.upstreamBinding?.conversationId ?? "";
|
|
143
|
+
const existingParentId = request.upstreamBinding?.parentId ?? "";
|
|
144
|
+
if (existingConversationId) {
|
|
145
|
+
return {
|
|
146
|
+
conversationId: existingConversationId,
|
|
147
|
+
parentId: existingParentId
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (profile.binding.firstTurn !== "empty" && profile.seedBinding?.conversationId) {
|
|
151
|
+
return {
|
|
152
|
+
conversationId: profile.seedBinding.conversationId,
|
|
153
|
+
parentId: profile.seedBinding.parentId ?? ""
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (!profile.bootstrap) {
|
|
157
|
+
return {
|
|
158
|
+
conversationId: "",
|
|
159
|
+
parentId: ""
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const bootstrapResponse = await sendConfiguredRequest(profile.bootstrap.request, profile, session, {
|
|
163
|
+
request,
|
|
164
|
+
prompt,
|
|
165
|
+
conversationId: "",
|
|
166
|
+
parentId: ""
|
|
167
|
+
});
|
|
168
|
+
const payload = await readJsonResponse(bootstrapResponse, request.providerId, "bootstrap");
|
|
169
|
+
const conversationId = extractFirstString(payload, profile.bootstrap.conversationIdPath);
|
|
170
|
+
if (!conversationId) {
|
|
171
|
+
throw new ProviderFailure({
|
|
172
|
+
kind: "permanent",
|
|
173
|
+
code: "request_invalid",
|
|
174
|
+
message: `Provider "${request.providerId}" bootstrap response did not contain a conversation id at "${profile.bootstrap.conversationIdPath}".`,
|
|
175
|
+
displayMessage: "Provider conversation bootstrap is misconfigured.",
|
|
176
|
+
retryable: false,
|
|
177
|
+
sessionResetEligible: false
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
conversationId,
|
|
182
|
+
parentId: profile.bootstrap.parentIdPath
|
|
183
|
+
? extractFirstString(payload, profile.bootstrap.parentIdPath)
|
|
184
|
+
: ""
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
async function sendConfiguredRequest(template, profile, session, context, preparedHeaders = {}) {
|
|
188
|
+
const renderContext = ensureDynamicTemplateState(context);
|
|
189
|
+
const headers = buildRequestHeaders(profile, session, template.headers, renderContext, preparedHeaders);
|
|
190
|
+
const signedRequest = applyConfiguredRequestSigning(template, session, renderContext, renderTemplateString(template.url, renderContext), headers);
|
|
191
|
+
const init = {
|
|
192
|
+
method: template.method,
|
|
193
|
+
headers: signedRequest.headers
|
|
194
|
+
};
|
|
195
|
+
if (template.body !== undefined) {
|
|
196
|
+
const renderedBody = renderTemplateValue(template.body, renderContext);
|
|
197
|
+
init.body =
|
|
198
|
+
profile.family === "http-connect"
|
|
199
|
+
? encodeConnectBody(renderedBody)
|
|
200
|
+
: typeof renderedBody === "string"
|
|
201
|
+
? renderedBody
|
|
202
|
+
: JSON.stringify(renderedBody);
|
|
203
|
+
if (!hasHeader(signedRequest.headers, "content-type")) {
|
|
204
|
+
signedRequest.headers["Content-Type"] =
|
|
205
|
+
profile.family === "http-connect" ? "application/connect+json" : "application/json";
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const response = await fetch(signedRequest.url, init);
|
|
209
|
+
if (!response.ok) {
|
|
210
|
+
const responsePreview = sanitizeProviderErrorPreview(await safeReadResponseText(response));
|
|
211
|
+
throw new ProviderFailure({
|
|
212
|
+
kind: response.status >= 500 ? "transient" : "permanent",
|
|
213
|
+
code: response.status >= 500 ? "transport_error" : "request_invalid",
|
|
214
|
+
message: `Provider request failed with status ${response.status} for ${signedRequest.url}.`,
|
|
215
|
+
displayMessage: `Provider request failed with HTTP ${response.status}.`,
|
|
216
|
+
retryable: response.status >= 500,
|
|
217
|
+
sessionResetEligible: response.status === 401 || response.status === 403,
|
|
218
|
+
details: {
|
|
219
|
+
stage: "request",
|
|
220
|
+
httpStatus: response.status,
|
|
221
|
+
responsePreview: responsePreview || undefined
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return response;
|
|
226
|
+
}
|
|
227
|
+
async function safeReadResponseText(response) {
|
|
228
|
+
try {
|
|
229
|
+
return await response.text();
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
return "";
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function sanitizeProviderErrorPreview(value, maxLength = 600) {
|
|
236
|
+
const normalized = sanitizeSensitiveText(value).replace(/\s+/g, " ").trim();
|
|
237
|
+
if (!normalized) {
|
|
238
|
+
return "";
|
|
239
|
+
}
|
|
240
|
+
return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength)}...`;
|
|
241
|
+
}
|
|
242
|
+
async function resolvePreparedRequestHeaders(profile, session, context) {
|
|
243
|
+
if (!profile.preflight) {
|
|
244
|
+
return {};
|
|
245
|
+
}
|
|
246
|
+
const response = await sendConfiguredRequest(profile.preflight.request, profile, session, context);
|
|
247
|
+
const payload = await readJsonResponse(response, context.request.providerId, "preflight");
|
|
248
|
+
const resolvedHeaders = Object.fromEntries(Object.entries(profile.preflight.headerBindings).flatMap(([headerName, path]) => {
|
|
249
|
+
const value = extractFirstString(payload, path);
|
|
250
|
+
return value ? [[headerName, value]] : [];
|
|
251
|
+
}));
|
|
252
|
+
if (!profile.preflight.proofOfWork) {
|
|
253
|
+
return resolvedHeaders;
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
...resolvedHeaders,
|
|
257
|
+
[profile.preflight.proofOfWork.headerName]: await renderProofOfWorkHeader(profile.preflight.proofOfWork, payload, context.request.providerId)
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function buildRequestHeaders(profile, session, configuredHeaders, context, preparedHeaders = {}) {
|
|
261
|
+
const headers = profile.session.includeExtraHeaders
|
|
262
|
+
? {
|
|
263
|
+
...(session.extraHeaders ?? {})
|
|
264
|
+
}
|
|
265
|
+
: {};
|
|
266
|
+
if (profile.session.requireCookie) {
|
|
267
|
+
headers.Cookie = requireSessionField(session.cookie, context.request.providerId, "cookie");
|
|
268
|
+
}
|
|
269
|
+
if (profile.session.requireBearerToken) {
|
|
270
|
+
headers.Authorization = `Bearer ${requireSessionField(session.bearerToken, context.request.providerId, "bearerToken")}`;
|
|
271
|
+
}
|
|
272
|
+
if (profile.session.requireUserAgent) {
|
|
273
|
+
headers["User-Agent"] = requireSessionField(session.userAgent, context.request.providerId, "userAgent");
|
|
274
|
+
}
|
|
275
|
+
for (const [key, value] of Object.entries(configuredHeaders)) {
|
|
276
|
+
headers[key] = renderTemplateString(value, context);
|
|
277
|
+
}
|
|
278
|
+
for (const [key, value] of Object.entries(preparedHeaders)) {
|
|
279
|
+
headers[key] = value;
|
|
280
|
+
}
|
|
281
|
+
return headers;
|
|
282
|
+
}
|
|
283
|
+
async function collectJsonCompletion(response, profile, providerId) {
|
|
284
|
+
const payload = await readJsonResponse(response, providerId, "request");
|
|
285
|
+
if (!matchesEventFilters(payload, profile.response.eventFilters)) {
|
|
286
|
+
return {
|
|
287
|
+
content: "",
|
|
288
|
+
responseId: "",
|
|
289
|
+
conversationId: "",
|
|
290
|
+
eventCount: 0,
|
|
291
|
+
fragmentCount: 0
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
const content = profile.response.contentPaths.map((path) => extractFirstString(payload, path)).find(Boolean) ??
|
|
295
|
+
"";
|
|
296
|
+
const responseId = profile.response.responseIdPaths
|
|
297
|
+
.map((path) => extractFirstString(payload, path))
|
|
298
|
+
.find(Boolean) ?? "";
|
|
299
|
+
const conversationId = profile.response.conversationIdPaths
|
|
300
|
+
.map((path) => extractFirstString(payload, path))
|
|
301
|
+
.find(Boolean) ?? "";
|
|
302
|
+
return {
|
|
303
|
+
content: profile.response.trimLeadingAssistantBlock
|
|
304
|
+
? normalizeLeadingAssistantBlock(content)
|
|
305
|
+
: content.trim(),
|
|
306
|
+
responseId,
|
|
307
|
+
conversationId,
|
|
308
|
+
eventCount: content ? 1 : 0,
|
|
309
|
+
fragmentCount: content ? 1 : 0
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
async function readJsonResponse(response, providerId, phase) {
|
|
313
|
+
try {
|
|
314
|
+
return (await response.json());
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
throw new ProviderFailure({
|
|
318
|
+
kind: "permanent",
|
|
319
|
+
code: "request_invalid",
|
|
320
|
+
message: `Provider "${providerId}" returned invalid JSON during ${phase}.`,
|
|
321
|
+
displayMessage: "Provider response format is invalid.",
|
|
322
|
+
retryable: false,
|
|
323
|
+
sessionResetEligible: false,
|
|
324
|
+
cause: error
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function renderTemplateValue(value, context) {
|
|
329
|
+
if (typeof value === "string") {
|
|
330
|
+
const exactTemplate = value.match(/^\{\{\s*(\w+)\s*\}\}$/);
|
|
331
|
+
if (exactTemplate) {
|
|
332
|
+
return resolveTemplateToken(exactTemplate[1], context);
|
|
333
|
+
}
|
|
334
|
+
return renderTemplateString(value, context);
|
|
335
|
+
}
|
|
336
|
+
if (Array.isArray(value)) {
|
|
337
|
+
return value
|
|
338
|
+
.map((entry) => renderTemplateValue(entry, context))
|
|
339
|
+
.filter((entry) => entry !== OMIT_TEMPLATE_VALUE);
|
|
340
|
+
}
|
|
341
|
+
if (typeof value === "object" && value !== null) {
|
|
342
|
+
return Object.fromEntries(Object.entries(value).flatMap(([key, entry]) => {
|
|
343
|
+
const rendered = renderTemplateValue(entry, context);
|
|
344
|
+
return rendered === OMIT_TEMPLATE_VALUE ? [] : [[key, rendered]];
|
|
345
|
+
}));
|
|
346
|
+
}
|
|
347
|
+
return value;
|
|
348
|
+
}
|
|
349
|
+
function renderTemplateString(template, context) {
|
|
350
|
+
return template.replace(/\{\{\s*(\w+)\s*\}\}/g, (_match, key) => {
|
|
351
|
+
const resolved = resolveTemplateToken(key, context);
|
|
352
|
+
return typeof resolved === "string" ? resolved : String(resolved ?? "");
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
function resolveTemplateToken(key, context) {
|
|
356
|
+
const dynamic = ensureDynamicTemplateState(context).dynamic;
|
|
357
|
+
const modelVariant = parseBridgeModelVariant(context.request.modelId);
|
|
358
|
+
switch (key) {
|
|
359
|
+
case "prompt":
|
|
360
|
+
return context.prompt;
|
|
361
|
+
case "modelId":
|
|
362
|
+
return modelVariant.baseModelId;
|
|
363
|
+
case "publicModelId":
|
|
364
|
+
return context.request.modelId;
|
|
365
|
+
case "providerId":
|
|
366
|
+
return context.request.providerId;
|
|
367
|
+
case "requestId":
|
|
368
|
+
return context.request.requestId;
|
|
369
|
+
case "requestUuid":
|
|
370
|
+
return dynamic.requestUuid;
|
|
371
|
+
case "messageId":
|
|
372
|
+
return dynamic.userMessageId;
|
|
373
|
+
case "userMessageId":
|
|
374
|
+
return dynamic.userMessageId;
|
|
375
|
+
case "assistantMessageId":
|
|
376
|
+
return dynamic.assistantMessageId;
|
|
377
|
+
case "sessionId":
|
|
378
|
+
return context.request.sessionId;
|
|
379
|
+
case "conversationId":
|
|
380
|
+
return context.conversationId;
|
|
381
|
+
case "conversationIdOrOmit":
|
|
382
|
+
return context.conversationId || OMIT_TEMPLATE_VALUE;
|
|
383
|
+
case "parentId":
|
|
384
|
+
return context.parentId;
|
|
385
|
+
case "parentIdOrClientCreatedRoot":
|
|
386
|
+
return context.parentId || "client-created-root";
|
|
387
|
+
case "parentIdOrOmit":
|
|
388
|
+
return context.parentId || OMIT_TEMPLATE_VALUE;
|
|
389
|
+
case "parentIdOrNull":
|
|
390
|
+
return context.parentId || null;
|
|
391
|
+
case "parentIdNumberOrNull":
|
|
392
|
+
return normalizeNumericTemplateValue(context.parentId);
|
|
393
|
+
case "parentIdNumberOrOmit": {
|
|
394
|
+
const normalized = normalizeNumericTemplateValue(context.parentId);
|
|
395
|
+
return normalized === null ? OMIT_TEMPLATE_VALUE : normalized;
|
|
396
|
+
}
|
|
397
|
+
case "unixTimestampSec":
|
|
398
|
+
return dynamic.unixTimestampSec;
|
|
399
|
+
case "unixTimestampMs":
|
|
400
|
+
return dynamic.unixTimestampMs;
|
|
401
|
+
case "currentDateTime":
|
|
402
|
+
return formatCurrentDateTime();
|
|
403
|
+
case "currentDate":
|
|
404
|
+
return formatCurrentDate();
|
|
405
|
+
case "currentTime":
|
|
406
|
+
return formatCurrentTime();
|
|
407
|
+
case "currentWeekday":
|
|
408
|
+
return formatCurrentWeekday();
|
|
409
|
+
case "currentTimezone":
|
|
410
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
411
|
+
case "userLanguage":
|
|
412
|
+
return "en-US";
|
|
413
|
+
case "userName":
|
|
414
|
+
return "User";
|
|
415
|
+
case "thinkingEnabled":
|
|
416
|
+
return modelVariant.thinkingEnabled ?? false;
|
|
417
|
+
case "thinkingEnabledOrTrue":
|
|
418
|
+
return modelVariant.thinkingEnabled ?? true;
|
|
419
|
+
case "thinkingEnabledOrFalse":
|
|
420
|
+
return modelVariant.thinkingEnabled ?? false;
|
|
421
|
+
case "null":
|
|
422
|
+
return null;
|
|
423
|
+
default:
|
|
424
|
+
return "";
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function parseBridgeModelVariant(modelId) {
|
|
428
|
+
const trimmed = modelId.trim();
|
|
429
|
+
if (trimmed.endsWith("@thinking")) {
|
|
430
|
+
return {
|
|
431
|
+
baseModelId: trimmed.slice(0, -"@thinking".length),
|
|
432
|
+
thinkingEnabled: true
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
if (trimmed.endsWith("@instant")) {
|
|
436
|
+
return {
|
|
437
|
+
baseModelId: trimmed.slice(0, -"@instant".length),
|
|
438
|
+
thinkingEnabled: false
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
if (trimmed.endsWith("@no-thinking")) {
|
|
442
|
+
return {
|
|
443
|
+
baseModelId: trimmed.slice(0, -"@no-thinking".length),
|
|
444
|
+
thinkingEnabled: false
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
baseModelId: trimmed,
|
|
449
|
+
thinkingEnabled: null
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
function normalizeNumericTemplateValue(value) {
|
|
453
|
+
const trimmed = value.trim();
|
|
454
|
+
if (!trimmed) {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
if (/^-?(?:0|[1-9]\d*)$/.test(trimmed)) {
|
|
458
|
+
const normalized = Number(trimmed);
|
|
459
|
+
if (Number.isSafeInteger(normalized)) {
|
|
460
|
+
return normalized;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return trimmed;
|
|
464
|
+
}
|
|
465
|
+
function ensureDynamicTemplateState(context) {
|
|
466
|
+
if (context.dynamic) {
|
|
467
|
+
return context;
|
|
468
|
+
}
|
|
469
|
+
const now = Date.now();
|
|
470
|
+
context.dynamic = {
|
|
471
|
+
requestUuid: randomUUID(),
|
|
472
|
+
userMessageId: randomUUID(),
|
|
473
|
+
assistantMessageId: randomUUID(),
|
|
474
|
+
unixTimestampSec: Math.floor(now / 1000),
|
|
475
|
+
unixTimestampMs: now
|
|
476
|
+
};
|
|
477
|
+
return context;
|
|
478
|
+
}
|
|
479
|
+
function applyConfiguredResponseIdFallback(completion, profile, context) {
|
|
480
|
+
if (completion.responseId || !profile.response.fallbackResponseId) {
|
|
481
|
+
return completion;
|
|
482
|
+
}
|
|
483
|
+
const dynamic = ensureDynamicTemplateState(context).dynamic;
|
|
484
|
+
const responseId = profile.response.fallbackResponseId === "assistantMessageId"
|
|
485
|
+
? dynamic.assistantMessageId
|
|
486
|
+
: dynamic.userMessageId;
|
|
487
|
+
return {
|
|
488
|
+
...completion,
|
|
489
|
+
responseId
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
function applyConfiguredRequestSigning(template, session, context, renderedUrl, headers) {
|
|
493
|
+
if (!template.signing) {
|
|
494
|
+
return {
|
|
495
|
+
url: renderedUrl,
|
|
496
|
+
headers
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
if (template.signing.kind !== "z-ai-v1") {
|
|
500
|
+
return {
|
|
501
|
+
url: renderedUrl,
|
|
502
|
+
headers
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
const prepared = buildZaiSignedRequest(renderedUrl, session, context);
|
|
506
|
+
return {
|
|
507
|
+
url: prepared.url,
|
|
508
|
+
headers: {
|
|
509
|
+
...headers,
|
|
510
|
+
"X-FE-Version": headers["X-FE-Version"] || "prod-fe-1.1.2",
|
|
511
|
+
"X-Signature": prepared.signature
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
function buildZaiSignedRequest(baseUrl, session, context) {
|
|
516
|
+
const timestamp = String(context.dynamic.unixTimestampMs);
|
|
517
|
+
const requestId = context.dynamic.requestUuid;
|
|
518
|
+
const bearerToken = session.bearerToken ?? "";
|
|
519
|
+
const userId = readSessionJwtField(session.bearerToken, "id");
|
|
520
|
+
const language = readPreferredLanguageFromSession(session.extraHeaders);
|
|
521
|
+
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
522
|
+
const currentUrl = `https://chat.z.ai/c/${context.conversationId}`;
|
|
523
|
+
const pathname = `/c/${context.conversationId}`;
|
|
524
|
+
const localTimeIso = new Date(context.dynamic.unixTimestampMs).toISOString();
|
|
525
|
+
const query = new URLSearchParams([
|
|
526
|
+
["timestamp", timestamp],
|
|
527
|
+
["requestId", requestId],
|
|
528
|
+
["user_id", userId],
|
|
529
|
+
["version", "0.0.1"],
|
|
530
|
+
["platform", "web"],
|
|
531
|
+
["token", bearerToken],
|
|
532
|
+
["user_agent", session.userAgent ?? ""],
|
|
533
|
+
["language", language],
|
|
534
|
+
["languages", language],
|
|
535
|
+
["timezone", timezone],
|
|
536
|
+
["cookie_enabled", "true"],
|
|
537
|
+
["current_url", currentUrl],
|
|
538
|
+
["pathname", pathname],
|
|
539
|
+
["search", ""],
|
|
540
|
+
["hash", ""],
|
|
541
|
+
["host", "chat.z.ai"],
|
|
542
|
+
["hostname", "chat.z.ai"],
|
|
543
|
+
["protocol", "https:"],
|
|
544
|
+
["referrer", ""],
|
|
545
|
+
["title", "New Chat | Z.ai - Free AI Chatbot & Agent powered by GLM-5.1 & GLM-5"],
|
|
546
|
+
["timezone_offset", String(-new Date().getTimezoneOffset())],
|
|
547
|
+
["local_time", localTimeIso],
|
|
548
|
+
["utc_time", new Date(context.dynamic.unixTimestampMs).toUTCString()],
|
|
549
|
+
["is_mobile", "false"],
|
|
550
|
+
["is_touch", "false"],
|
|
551
|
+
["max_touch_points", "0"],
|
|
552
|
+
["browser_name", "Unknown"],
|
|
553
|
+
["os_name", "Unknown"],
|
|
554
|
+
["signature_timestamp", timestamp]
|
|
555
|
+
]);
|
|
556
|
+
const signature = buildZaiSignature([
|
|
557
|
+
["requestId", requestId],
|
|
558
|
+
["timestamp", timestamp],
|
|
559
|
+
["user_id", userId]
|
|
560
|
+
], context.prompt.trim(), timestamp);
|
|
561
|
+
const url = new URL(baseUrl);
|
|
562
|
+
for (const [key, value] of query.entries()) {
|
|
563
|
+
url.searchParams.set(key, value);
|
|
564
|
+
}
|
|
565
|
+
return {
|
|
566
|
+
url: url.toString(),
|
|
567
|
+
signature
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
function buildZaiSignature(entries, prompt, timestamp) {
|
|
571
|
+
const sortedPayload = [...entries]
|
|
572
|
+
.sort((left, right) => left[0].localeCompare(right[0]))
|
|
573
|
+
.flatMap(([key, value]) => [key, value])
|
|
574
|
+
.join(",");
|
|
575
|
+
const promptBase64 = Buffer.from(prompt, "utf8").toString("base64");
|
|
576
|
+
const data = `${sortedPayload}|${promptBase64}|${timestamp}`;
|
|
577
|
+
const bucket = Math.floor(Number(timestamp) / (5 * 60 * 1000));
|
|
578
|
+
const bucketKey = createHmac("sha256", "key-@@@@)))()((9))-xxxx&&&%%%%%")
|
|
579
|
+
.update(String(bucket))
|
|
580
|
+
.digest("hex");
|
|
581
|
+
return createHmac("sha256", bucketKey).update(data).digest("hex");
|
|
582
|
+
}
|
|
583
|
+
function matchesEventFilters(payload, filters) {
|
|
584
|
+
if (filters.length === 0) {
|
|
585
|
+
return true;
|
|
586
|
+
}
|
|
587
|
+
return filters.every((filter) => {
|
|
588
|
+
const values = extractPathValues(payload, filter.path.split(".").filter(Boolean));
|
|
589
|
+
return values.some((value) => value === filter.equals);
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
function readPreferredLanguageFromSession(headers) {
|
|
593
|
+
const acceptLanguage = Object.entries(headers ?? {})
|
|
594
|
+
.find(([key]) => key.toLowerCase() === "accept-language")?.[1]
|
|
595
|
+
?.trim() ?? "";
|
|
596
|
+
if (!acceptLanguage) {
|
|
597
|
+
return "en-US";
|
|
598
|
+
}
|
|
599
|
+
return (acceptLanguage
|
|
600
|
+
.split(",")
|
|
601
|
+
.map((entry) => entry.split(";")[0]?.trim())
|
|
602
|
+
.find(Boolean) ?? "en-US");
|
|
603
|
+
}
|
|
604
|
+
function readSessionJwtField(token, field) {
|
|
605
|
+
const source = token?.trim();
|
|
606
|
+
if (!source) {
|
|
607
|
+
return "";
|
|
608
|
+
}
|
|
609
|
+
const parts = source.split(".");
|
|
610
|
+
if (parts.length < 2) {
|
|
611
|
+
return "";
|
|
612
|
+
}
|
|
613
|
+
try {
|
|
614
|
+
const normalized = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
615
|
+
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
|
|
616
|
+
const payload = JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
|
|
617
|
+
return typeof payload[field] === "string" ? payload[field] : "";
|
|
618
|
+
}
|
|
619
|
+
catch {
|
|
620
|
+
return "";
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function formatCurrentDateTime() {
|
|
624
|
+
const now = new Date();
|
|
625
|
+
return `${formatCurrentDate(now)} ${formatCurrentTime(now)}`;
|
|
626
|
+
}
|
|
627
|
+
function formatCurrentDate(now = new Date()) {
|
|
628
|
+
return [
|
|
629
|
+
now.getFullYear(),
|
|
630
|
+
String(now.getMonth() + 1).padStart(2, "0"),
|
|
631
|
+
String(now.getDate()).padStart(2, "0")
|
|
632
|
+
].join("-");
|
|
633
|
+
}
|
|
634
|
+
function formatCurrentTime(now = new Date()) {
|
|
635
|
+
return [
|
|
636
|
+
String(now.getHours()).padStart(2, "0"),
|
|
637
|
+
String(now.getMinutes()).padStart(2, "0"),
|
|
638
|
+
String(now.getSeconds()).padStart(2, "0")
|
|
639
|
+
].join(":");
|
|
640
|
+
}
|
|
641
|
+
function formatCurrentWeekday(now = new Date()) {
|
|
642
|
+
return new Intl.DateTimeFormat("en-US", { weekday: "long" }).format(now);
|
|
643
|
+
}
|
|
644
|
+
function extractFirstString(payload, path) {
|
|
645
|
+
for (const value of extractPathValues(payload, path.split(".").filter(Boolean))) {
|
|
646
|
+
if (typeof value === "string" && value.trim()) {
|
|
647
|
+
return value.trim();
|
|
648
|
+
}
|
|
649
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
650
|
+
return String(value);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return "";
|
|
654
|
+
}
|
|
655
|
+
function extractFirstNumber(payload, path) {
|
|
656
|
+
for (const value of extractPathValues(payload, path.split(".").filter(Boolean))) {
|
|
657
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
658
|
+
return value;
|
|
659
|
+
}
|
|
660
|
+
if (typeof value === "string") {
|
|
661
|
+
const normalized = Number(value.trim());
|
|
662
|
+
if (Number.isFinite(normalized)) {
|
|
663
|
+
return normalized;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
async function renderProofOfWorkHeader(proof, payload, providerId) {
|
|
670
|
+
if (!proof) {
|
|
671
|
+
return "";
|
|
672
|
+
}
|
|
673
|
+
const algorithm = requiredPathString(payload, proof.algorithmPath, providerId, "preflight");
|
|
674
|
+
const challenge = requiredPathString(payload, proof.challengePath, providerId, "preflight");
|
|
675
|
+
const salt = requiredPathString(payload, proof.saltPath, providerId, "preflight");
|
|
676
|
+
const signature = requiredPathString(payload, proof.signaturePath, providerId, "preflight");
|
|
677
|
+
const difficulty = requiredPathNumber(payload, proof.difficultyPath, providerId, "preflight");
|
|
678
|
+
const expireAt = requiredPathNumber(payload, proof.expireAtPath, providerId, "preflight");
|
|
679
|
+
const targetPath = proof.targetPathPath ? extractFirstString(payload, proof.targetPathPath) : "";
|
|
680
|
+
if (proof.kind !== "sha3-wasm-salt-expiry") {
|
|
681
|
+
throw new ProviderFailure({
|
|
682
|
+
kind: "permanent",
|
|
683
|
+
code: "request_invalid",
|
|
684
|
+
message: `Provider "${providerId}" uses an unsupported proof-of-work configuration.`,
|
|
685
|
+
displayMessage: "Provider proof-of-work configuration is invalid.",
|
|
686
|
+
retryable: false,
|
|
687
|
+
sessionResetEligible: false
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
let answer;
|
|
691
|
+
try {
|
|
692
|
+
answer = await solveSha3WasmSaltExpiryProof({
|
|
693
|
+
wasmUrl: proof.wasmUrl,
|
|
694
|
+
challenge,
|
|
695
|
+
salt,
|
|
696
|
+
expireAt,
|
|
697
|
+
difficulty
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
catch (error) {
|
|
701
|
+
throw new ProviderFailure({
|
|
702
|
+
kind: "permanent",
|
|
703
|
+
code: "request_invalid",
|
|
704
|
+
message: `Provider "${providerId}" proof-of-work preparation failed.`,
|
|
705
|
+
displayMessage: "Provider request preparation failed.",
|
|
706
|
+
retryable: false,
|
|
707
|
+
sessionResetEligible: false,
|
|
708
|
+
cause: error
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
return Buffer.from(JSON.stringify({
|
|
712
|
+
algorithm,
|
|
713
|
+
challenge,
|
|
714
|
+
salt,
|
|
715
|
+
answer,
|
|
716
|
+
signature,
|
|
717
|
+
...(targetPath ? { target_path: targetPath } : {})
|
|
718
|
+
}), "utf8").toString("base64");
|
|
719
|
+
}
|
|
720
|
+
function requiredPathString(payload, path, providerId, phase) {
|
|
721
|
+
const value = extractFirstString(payload, path);
|
|
722
|
+
if (value) {
|
|
723
|
+
return value;
|
|
724
|
+
}
|
|
725
|
+
throw new ProviderFailure({
|
|
726
|
+
kind: "permanent",
|
|
727
|
+
code: "request_invalid",
|
|
728
|
+
message: `Provider "${providerId}" ${phase} response did not contain a string value at "${path}".`,
|
|
729
|
+
displayMessage: "Provider request preparation is misconfigured.",
|
|
730
|
+
retryable: false,
|
|
731
|
+
sessionResetEligible: false
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
function requiredPathNumber(payload, path, providerId, phase) {
|
|
735
|
+
const value = extractFirstNumber(payload, path);
|
|
736
|
+
if (value !== null) {
|
|
737
|
+
return value;
|
|
738
|
+
}
|
|
739
|
+
throw new ProviderFailure({
|
|
740
|
+
kind: "permanent",
|
|
741
|
+
code: "request_invalid",
|
|
742
|
+
message: `Provider "${providerId}" ${phase} response did not contain a numeric value at "${path}".`,
|
|
743
|
+
displayMessage: "Provider request preparation is misconfigured.",
|
|
744
|
+
retryable: false,
|
|
745
|
+
sessionResetEligible: false
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
const wasmPowModuleCache = new Map();
|
|
749
|
+
async function solveSha3WasmSaltExpiryProof(input) {
|
|
750
|
+
const wasm = await loadWasmPowModule(input.wasmUrl);
|
|
751
|
+
let cachedBytes = null;
|
|
752
|
+
let cachedView = null;
|
|
753
|
+
let vectorLength = 0;
|
|
754
|
+
const encoder = new TextEncoder();
|
|
755
|
+
const memoryBytes = () => {
|
|
756
|
+
if (!cachedBytes || cachedBytes.buffer !== wasm.memory.buffer) {
|
|
757
|
+
cachedBytes = new Uint8Array(wasm.memory.buffer);
|
|
758
|
+
}
|
|
759
|
+
return cachedBytes;
|
|
760
|
+
};
|
|
761
|
+
const memoryView = () => {
|
|
762
|
+
if (!cachedView || cachedView.buffer !== wasm.memory.buffer) {
|
|
763
|
+
cachedView = new DataView(wasm.memory.buffer);
|
|
764
|
+
}
|
|
765
|
+
return cachedView;
|
|
766
|
+
};
|
|
767
|
+
const passString = (value) => {
|
|
768
|
+
let length = value.length;
|
|
769
|
+
let pointer = wasm.__wbindgen_export_0(length, 1) >>> 0;
|
|
770
|
+
let offset = 0;
|
|
771
|
+
const bytes = memoryBytes();
|
|
772
|
+
for (; offset < length; offset += 1) {
|
|
773
|
+
const code = value.charCodeAt(offset);
|
|
774
|
+
if (code > 0x7f) {
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
bytes[pointer + offset] = code;
|
|
778
|
+
}
|
|
779
|
+
if (offset !== length) {
|
|
780
|
+
if (offset !== 0) {
|
|
781
|
+
value = value.slice(offset);
|
|
782
|
+
}
|
|
783
|
+
pointer =
|
|
784
|
+
wasm.__wbindgen_export_1(pointer, length, (length = offset + value.length * 3), 1) >>> 0;
|
|
785
|
+
const target = memoryBytes().subarray(pointer + offset, pointer + length);
|
|
786
|
+
const encoded = encoder.encodeInto(value, target);
|
|
787
|
+
offset += encoded.written;
|
|
788
|
+
pointer = wasm.__wbindgen_export_1(pointer, length, offset, 1) >>> 0;
|
|
789
|
+
}
|
|
790
|
+
vectorLength = offset;
|
|
791
|
+
return pointer;
|
|
792
|
+
};
|
|
793
|
+
const prefix = `${input.salt}_${input.expireAt}_`;
|
|
794
|
+
const returnPointer = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
795
|
+
const challengePointer = passString(input.challenge);
|
|
796
|
+
const challengeLength = vectorLength;
|
|
797
|
+
const prefixPointer = passString(prefix);
|
|
798
|
+
const prefixLength = vectorLength;
|
|
799
|
+
wasm.wasm_solve(returnPointer, challengePointer, challengeLength, prefixPointer, prefixLength, input.difficulty);
|
|
800
|
+
const solved = memoryView().getInt32(returnPointer, true);
|
|
801
|
+
const answer = memoryView().getFloat64(returnPointer + 8, true);
|
|
802
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
803
|
+
if (solved !== 1 || !Number.isFinite(answer)) {
|
|
804
|
+
throw new Error(`Proof-of-work solve failed for ${input.wasmUrl}.`);
|
|
805
|
+
}
|
|
806
|
+
return answer;
|
|
807
|
+
}
|
|
808
|
+
async function loadWasmPowModule(wasmUrl) {
|
|
809
|
+
let cached = wasmPowModuleCache.get(wasmUrl);
|
|
810
|
+
if (!cached) {
|
|
811
|
+
cached = (async () => {
|
|
812
|
+
const response = await fetch(wasmUrl);
|
|
813
|
+
if (!response.ok) {
|
|
814
|
+
throw new Error(`Failed to load proof-of-work module from ${wasmUrl}.`);
|
|
815
|
+
}
|
|
816
|
+
const bytes = await response.arrayBuffer();
|
|
817
|
+
const { instance } = await WebAssembly.instantiate(bytes, {
|
|
818
|
+
wbg: {}
|
|
819
|
+
});
|
|
820
|
+
return instance.exports;
|
|
821
|
+
})();
|
|
822
|
+
wasmPowModuleCache.set(wasmUrl, cached);
|
|
823
|
+
}
|
|
824
|
+
return await cached;
|
|
825
|
+
}
|
|
826
|
+
function wrapVisibleTextAsFinalPacket(content) {
|
|
827
|
+
const trimmed = content.trim();
|
|
828
|
+
if (!trimmed || looksLikeAssistantPacket(trimmed)) {
|
|
829
|
+
return trimmed;
|
|
830
|
+
}
|
|
831
|
+
const sanitized = sanitizeBridgeApiOutput(trimmed);
|
|
832
|
+
if (sanitized.sanitized) {
|
|
833
|
+
return trimmed;
|
|
834
|
+
}
|
|
835
|
+
return `<final>${trimmed}</final>`;
|
|
836
|
+
}
|
|
837
|
+
async function* maybeWrapVisibleTextFinalStream(stream, allowVisibleTextFinal) {
|
|
838
|
+
if (!allowVisibleTextFinal) {
|
|
839
|
+
yield* stream;
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
let started = false;
|
|
843
|
+
let passthroughAssistantPacket = false;
|
|
844
|
+
let bufferedContent = "";
|
|
845
|
+
let responseId = "";
|
|
846
|
+
let conversationId = "";
|
|
847
|
+
for await (const fragment of stream) {
|
|
848
|
+
responseId = fragment.responseId;
|
|
849
|
+
conversationId = fragment.conversationId;
|
|
850
|
+
if (!started && !passthroughAssistantPacket) {
|
|
851
|
+
bufferedContent += fragment.content;
|
|
852
|
+
const trimmed = bufferedContent.trimStart();
|
|
853
|
+
if (!trimmed) {
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
if (looksLikeAssistantPacket(trimmed)) {
|
|
857
|
+
started = true;
|
|
858
|
+
passthroughAssistantPacket = true;
|
|
859
|
+
yield {
|
|
860
|
+
...fragment,
|
|
861
|
+
content: bufferedContent
|
|
862
|
+
};
|
|
863
|
+
bufferedContent = "";
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
if (trimmed.startsWith("<") && trimmed.length < "<zc_packet".length) {
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
started = true;
|
|
870
|
+
yield {
|
|
871
|
+
content: "<final>",
|
|
872
|
+
responseId,
|
|
873
|
+
conversationId,
|
|
874
|
+
eventCountDelta: 0,
|
|
875
|
+
fragmentCountDelta: 0
|
|
876
|
+
};
|
|
877
|
+
yield {
|
|
878
|
+
...fragment,
|
|
879
|
+
content: bufferedContent
|
|
880
|
+
};
|
|
881
|
+
bufferedContent = "";
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
yield fragment;
|
|
885
|
+
}
|
|
886
|
+
if (!started || passthroughAssistantPacket) {
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
yield {
|
|
890
|
+
content: "</final>",
|
|
891
|
+
responseId,
|
|
892
|
+
conversationId,
|
|
893
|
+
eventCountDelta: 0,
|
|
894
|
+
fragmentCountDelta: 0
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
function looksLikeAssistantPacket(content) {
|
|
898
|
+
return /^<(?:final|tool|zc_packet|packet)\b/i.test(content);
|
|
899
|
+
}
|
|
900
|
+
function extractPathValues(value, segments) {
|
|
901
|
+
if (segments.length === 0) {
|
|
902
|
+
return [value];
|
|
903
|
+
}
|
|
904
|
+
if (value === null || value === undefined) {
|
|
905
|
+
return [];
|
|
906
|
+
}
|
|
907
|
+
const [segment, ...rest] = segments;
|
|
908
|
+
if (segment === "*") {
|
|
909
|
+
if (Array.isArray(value)) {
|
|
910
|
+
return value.flatMap((entry) => extractPathValues(entry, rest));
|
|
911
|
+
}
|
|
912
|
+
if (typeof value === "object") {
|
|
913
|
+
return Object.values(value).flatMap((entry) => extractPathValues(entry, rest));
|
|
914
|
+
}
|
|
915
|
+
return [];
|
|
916
|
+
}
|
|
917
|
+
if (Array.isArray(value)) {
|
|
918
|
+
const index = Number.parseInt(segment, 10);
|
|
919
|
+
if (!Number.isInteger(index)) {
|
|
920
|
+
return [];
|
|
921
|
+
}
|
|
922
|
+
return extractPathValues(value[index], rest);
|
|
923
|
+
}
|
|
924
|
+
if (typeof value === "object") {
|
|
925
|
+
return extractPathValues(value[segment], rest);
|
|
926
|
+
}
|
|
927
|
+
return [];
|
|
928
|
+
}
|
|
929
|
+
function requireSessionField(value, providerId, field) {
|
|
930
|
+
if (typeof value === "string" && value.trim()) {
|
|
931
|
+
return value;
|
|
932
|
+
}
|
|
933
|
+
throw new ProviderFailure({
|
|
934
|
+
kind: "permanent",
|
|
935
|
+
code: "authentication_failed",
|
|
936
|
+
message: `Provider "${providerId}" session is missing required field "${field}".`,
|
|
937
|
+
displayMessage: "Provider authentication/session state is incomplete.",
|
|
938
|
+
retryable: false,
|
|
939
|
+
sessionResetEligible: false
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
function hasHeader(headers, name) {
|
|
943
|
+
const target = name.toLowerCase();
|
|
944
|
+
return Object.keys(headers).some((key) => key.toLowerCase() === target);
|
|
945
|
+
}
|
|
946
|
+
async function* singleFragmentStream(content, responseId, conversationId) {
|
|
947
|
+
if (!content) {
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
yield {
|
|
951
|
+
content,
|
|
952
|
+
responseId,
|
|
953
|
+
conversationId,
|
|
954
|
+
eventCountDelta: 1,
|
|
955
|
+
fragmentCountDelta: 1
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
function encodeConnectBody(value) {
|
|
959
|
+
const payloadText = typeof value === "string" ? value : JSON.stringify(value);
|
|
960
|
+
const payloadBytes = new TextEncoder().encode(payloadText);
|
|
961
|
+
const buffer = new Uint8Array(5 + payloadBytes.length);
|
|
962
|
+
buffer[0] = 0;
|
|
963
|
+
buffer[1] = (payloadBytes.length >>> 24) & 0xff;
|
|
964
|
+
buffer[2] = (payloadBytes.length >>> 16) & 0xff;
|
|
965
|
+
buffer[3] = (payloadBytes.length >>> 8) & 0xff;
|
|
966
|
+
buffer[4] = payloadBytes.length & 0xff;
|
|
967
|
+
buffer.set(payloadBytes, 5);
|
|
968
|
+
return buffer;
|
|
969
|
+
}
|
|
970
|
+
export const genericProviderTransportModule = {
|
|
971
|
+
collectGenericProviderCompletion,
|
|
972
|
+
openGenericProviderStream
|
|
973
|
+
};
|