@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.
Files changed (145) hide show
  1. package/README.md +117 -0
  2. package/bin/openbridge.js +10 -0
  3. package/package.json +85 -0
  4. package/packages/cli/dist/args.d.ts +30 -0
  5. package/packages/cli/dist/args.js +160 -0
  6. package/packages/cli/dist/cli.d.ts +2 -0
  7. package/packages/cli/dist/cli.js +9 -0
  8. package/packages/cli/dist/index.d.ts +26 -0
  9. package/packages/cli/dist/index.js +76 -0
  10. package/packages/runtime/dist/assistant-protocol.d.ts +34 -0
  11. package/packages/runtime/dist/assistant-protocol.js +121 -0
  12. package/packages/runtime/dist/execution/in-process.d.ts +14 -0
  13. package/packages/runtime/dist/execution/in-process.js +45 -0
  14. package/packages/runtime/dist/execution/types.d.ts +49 -0
  15. package/packages/runtime/dist/execution/types.js +20 -0
  16. package/packages/runtime/dist/index.d.ts +86 -0
  17. package/packages/runtime/dist/index.js +60 -0
  18. package/packages/runtime/dist/normalizers/index.d.ts +6 -0
  19. package/packages/runtime/dist/normalizers/index.js +12 -0
  20. package/packages/runtime/dist/normalizers/legacy-packet.d.ts +6 -0
  21. package/packages/runtime/dist/normalizers/legacy-packet.js +131 -0
  22. package/packages/runtime/dist/output-sanitizer.d.ts +23 -0
  23. package/packages/runtime/dist/output-sanitizer.js +78 -0
  24. package/packages/runtime/dist/packet-extractor.d.ts +17 -0
  25. package/packages/runtime/dist/packet-extractor.js +43 -0
  26. package/packages/runtime/dist/packet-normalizer.d.ts +21 -0
  27. package/packages/runtime/dist/packet-normalizer.js +47 -0
  28. package/packages/runtime/dist/prompt-compiler.d.ts +28 -0
  29. package/packages/runtime/dist/prompt-compiler.js +301 -0
  30. package/packages/runtime/dist/protocol.d.ts +44 -0
  31. package/packages/runtime/dist/protocol.js +165 -0
  32. package/packages/runtime/dist/provider-failure.d.ts +52 -0
  33. package/packages/runtime/dist/provider-failure.js +236 -0
  34. package/packages/runtime/dist/provider.d.ts +40 -0
  35. package/packages/runtime/dist/provider.js +1 -0
  36. package/packages/runtime/dist/runtime.d.ts +86 -0
  37. package/packages/runtime/dist/runtime.js +462 -0
  38. package/packages/runtime/dist/session-bound-provider.d.ts +52 -0
  39. package/packages/runtime/dist/session-bound-provider.js +366 -0
  40. package/packages/runtime/dist/tool-name-aliases.d.ts +5 -0
  41. package/packages/runtime/dist/tool-name-aliases.js +13 -0
  42. package/packages/runtime/dist/tools/bash.d.ts +9 -0
  43. package/packages/runtime/dist/tools/bash.js +157 -0
  44. package/packages/runtime/dist/tools/edit.d.ts +9 -0
  45. package/packages/runtime/dist/tools/edit.js +94 -0
  46. package/packages/runtime/dist/tools/index.d.ts +39 -0
  47. package/packages/runtime/dist/tools/index.js +27 -0
  48. package/packages/runtime/dist/tools/list-dir.d.ts +9 -0
  49. package/packages/runtime/dist/tools/list-dir.js +127 -0
  50. package/packages/runtime/dist/tools/read.d.ts +9 -0
  51. package/packages/runtime/dist/tools/read.js +56 -0
  52. package/packages/runtime/dist/tools/registry.d.ts +15 -0
  53. package/packages/runtime/dist/tools/registry.js +38 -0
  54. package/packages/runtime/dist/tools/runtime-path.d.ts +7 -0
  55. package/packages/runtime/dist/tools/runtime-path.js +22 -0
  56. package/packages/runtime/dist/tools/search-files.d.ts +9 -0
  57. package/packages/runtime/dist/tools/search-files.js +149 -0
  58. package/packages/runtime/dist/tools/text-file.d.ts +32 -0
  59. package/packages/runtime/dist/tools/text-file.js +101 -0
  60. package/packages/runtime/dist/tools/workspace-path.d.ts +17 -0
  61. package/packages/runtime/dist/tools/workspace-path.js +70 -0
  62. package/packages/runtime/dist/tools/write.d.ts +9 -0
  63. package/packages/runtime/dist/tools/write.js +59 -0
  64. package/packages/server/dist/bridge/bridge-model-catalog.d.ts +56 -0
  65. package/packages/server/dist/bridge/bridge-model-catalog.js +100 -0
  66. package/packages/server/dist/bridge/bridge-runtime-service.d.ts +61 -0
  67. package/packages/server/dist/bridge/bridge-runtime-service.js +1386 -0
  68. package/packages/server/dist/bridge/chat-completions/chat-completion-service.d.ts +127 -0
  69. package/packages/server/dist/bridge/chat-completions/chat-completion-service.js +1026 -0
  70. package/packages/server/dist/bridge/index.d.ts +335 -0
  71. package/packages/server/dist/bridge/index.js +45 -0
  72. package/packages/server/dist/bridge/live-provider-extraction-canary.d.ts +69 -0
  73. package/packages/server/dist/bridge/live-provider-extraction-canary.js +186 -0
  74. package/packages/server/dist/bridge/providers/generic-provider-transport.d.ts +53 -0
  75. package/packages/server/dist/bridge/providers/generic-provider-transport.js +973 -0
  76. package/packages/server/dist/bridge/providers/provider-session-resolver.d.ts +17 -0
  77. package/packages/server/dist/bridge/providers/provider-session-resolver.js +95 -0
  78. package/packages/server/dist/bridge/providers/provider-streams.d.ts +80 -0
  79. package/packages/server/dist/bridge/providers/provider-streams.js +844 -0
  80. package/packages/server/dist/bridge/providers/provider-transport-profile.d.ts +194 -0
  81. package/packages/server/dist/bridge/providers/provider-transport-profile.js +198 -0
  82. package/packages/server/dist/bridge/providers/web-provider-transport.d.ts +30 -0
  83. package/packages/server/dist/bridge/providers/web-provider-transport.js +151 -0
  84. package/packages/server/dist/bridge/state/file-bridge-state-store.d.ts +36 -0
  85. package/packages/server/dist/bridge/state/file-bridge-state-store.js +164 -0
  86. package/packages/server/dist/bridge/stores/local-session-package-store.d.ts +23 -0
  87. package/packages/server/dist/bridge/stores/local-session-package-store.js +548 -0
  88. package/packages/server/dist/bridge/stores/provider-store.d.ts +94 -0
  89. package/packages/server/dist/bridge/stores/provider-store.js +143 -0
  90. package/packages/server/dist/bridge/stores/session-backed-provider-store.d.ts +7 -0
  91. package/packages/server/dist/bridge/stores/session-backed-provider-store.js +26 -0
  92. package/packages/server/dist/bridge/stores/session-package-store.d.ts +286 -0
  93. package/packages/server/dist/bridge/stores/session-package-store.js +1527 -0
  94. package/packages/server/dist/bridge/stores/session-store.d.ts +120 -0
  95. package/packages/server/dist/bridge/stores/session-store.js +139 -0
  96. package/packages/server/dist/cli/index.d.ts +9 -0
  97. package/packages/server/dist/cli/index.js +6 -0
  98. package/packages/server/dist/cli/main.d.ts +2 -0
  99. package/packages/server/dist/cli/main.js +9 -0
  100. package/packages/server/dist/cli/run-bridge-server-cli.d.ts +54 -0
  101. package/packages/server/dist/cli/run-bridge-server-cli.js +371 -0
  102. package/packages/server/dist/client/bridge-api-client.d.ts +61 -0
  103. package/packages/server/dist/client/bridge-api-client.js +267 -0
  104. package/packages/server/dist/client/index.d.ts +11 -0
  105. package/packages/server/dist/client/index.js +11 -0
  106. package/packages/server/dist/config/bridge-server-config.d.ts +52 -0
  107. package/packages/server/dist/config/bridge-server-config.js +118 -0
  108. package/packages/server/dist/config/index.d.ts +20 -0
  109. package/packages/server/dist/config/index.js +8 -0
  110. package/packages/server/dist/http/bridge-api-route-context.d.ts +14 -0
  111. package/packages/server/dist/http/bridge-api-route-context.js +1 -0
  112. package/packages/server/dist/http/create-bridge-api-server.d.ts +72 -0
  113. package/packages/server/dist/http/create-bridge-api-server.js +225 -0
  114. package/packages/server/dist/http/index.d.ts +5 -0
  115. package/packages/server/dist/http/index.js +5 -0
  116. package/packages/server/dist/http/parse-request.d.ts +6 -0
  117. package/packages/server/dist/http/parse-request.js +27 -0
  118. package/packages/server/dist/http/register-bridge-api-routes.d.ts +7 -0
  119. package/packages/server/dist/http/register-bridge-api-routes.js +17 -0
  120. package/packages/server/dist/http/routes/admin-routes.d.ts +7 -0
  121. package/packages/server/dist/http/routes/admin-routes.js +135 -0
  122. package/packages/server/dist/http/routes/chat-completions-route.d.ts +7 -0
  123. package/packages/server/dist/http/routes/chat-completions-route.js +49 -0
  124. package/packages/server/dist/http/routes/health-routes.d.ts +6 -0
  125. package/packages/server/dist/http/routes/health-routes.js +7 -0
  126. package/packages/server/dist/http/routes/message-routes.d.ts +7 -0
  127. package/packages/server/dist/http/routes/message-routes.js +7 -0
  128. package/packages/server/dist/index.d.ts +85 -0
  129. package/packages/server/dist/index.js +28 -0
  130. package/packages/server/dist/security/bridge-auth.d.ts +9 -0
  131. package/packages/server/dist/security/bridge-auth.js +41 -0
  132. package/packages/server/dist/security/cors-policy.d.ts +5 -0
  133. package/packages/server/dist/security/cors-policy.js +34 -0
  134. package/packages/server/dist/security/index.d.ts +16 -0
  135. package/packages/server/dist/security/index.js +12 -0
  136. package/packages/server/dist/security/redact-sensitive-values.d.ts +19 -0
  137. package/packages/server/dist/security/redact-sensitive-values.js +67 -0
  138. package/packages/server/dist/shared/api-schema.d.ts +133 -0
  139. package/packages/server/dist/shared/api-schema.js +1 -0
  140. package/packages/server/dist/shared/bridge-api-error.d.ts +17 -0
  141. package/packages/server/dist/shared/bridge-api-error.js +19 -0
  142. package/packages/server/dist/shared/index.d.ts +7 -0
  143. package/packages/server/dist/shared/index.js +7 -0
  144. package/packages/server/dist/shared/output.d.ts +5 -0
  145. package/packages/server/dist/shared/output.js +14 -0
@@ -0,0 +1,1386 @@
1
+ import crypto from "node:crypto";
2
+ import { bridgeRuntime } from "@uncensoredcode/openbridge/runtime";
3
+ import { bridgeApiErrorModule } from "../shared/bridge-api-error.js";
4
+ import { outputModule } from "../shared/output.js";
5
+ import { bridgeModelCatalogModule } from "./bridge-model-catalog.js";
6
+ import { providerSessionResolverModule } from "./providers/provider-session-resolver.js";
7
+ import { providerStreamsModule } from "./providers/provider-streams.js";
8
+ import { webProviderTransportModule } from "./providers/web-provider-transport.js";
9
+ import { fileBridgeStateStoreModule } from "./state/file-bridge-state-store.js";
10
+ const { classifyProviderTransportError, compileProviderTurn, createMessagePacket, createToolRequestPacket, extractPacketCandidate, parseAssistantResponse, parseZcPacket, serializeAssistantResponse, InProcessToolExecutor, normalizeProviderToolName, normalizeProviderPacket, ProviderFailure, SessionBoundProviderAdapter, createDefaultRuntimeTools, createSecondaryRuntimeTools, runBridgeRuntime, serializeProviderFailure } = bridgeRuntime;
11
+ const { BridgeApiError } = bridgeApiErrorModule;
12
+ const { sanitizeBridgeApiOutput } = outputModule;
13
+ const { defaultModelForProvider } = bridgeModelCatalogModule;
14
+ const { createBridgeProviderSessionResolver } = providerSessionResolverModule;
15
+ const { extractIncrementalPacketMessage } = providerStreamsModule;
16
+ const { WebProviderTransport } = webProviderTransportModule;
17
+ const { FileBridgeStateStore } = fileBridgeStateStoreModule;
18
+ function createBridgeRuntimeService(dependencies) {
19
+ const stateStore = new FileBridgeStateStore(dependencies.config.stateRoot);
20
+ const sessionBindingStore = dependencies.sessionBindingStore ?? stateStore;
21
+ const emitLog = dependencies.onLog ?? defaultLogEvent;
22
+ const transport = dependencies.transport ??
23
+ new WebProviderTransport({
24
+ providerSessionResolver: createBridgeProviderSessionResolver({
25
+ sessionPackageStore: dependencies.sessionPackageStore,
26
+ stateStore
27
+ }),
28
+ loadProvider: dependencies.loadProvider
29
+ });
30
+ return {
31
+ async respond(request, pathSessionId) {
32
+ const normalized = normalizeBridgeRequest(request, pathSessionId, dependencies.config, dependencies.loadProvider);
33
+ const sessionHistory = await stateStore.loadSessionHistory(normalized.sessionId);
34
+ return executeBridgeRequest({
35
+ ...normalized,
36
+ sessionHistory,
37
+ persistSession: true
38
+ });
39
+ },
40
+ async execute(request) {
41
+ return executeBridgeRequest({
42
+ sessionId: request.sessionId,
43
+ input: request.input,
44
+ providerId: request.providerId,
45
+ modelId: request.modelId,
46
+ metadata: request.metadata,
47
+ toolProfile: request.toolProfile ?? "default",
48
+ sessionHistory: request.sessionHistory ?? [],
49
+ persistSession: request.persistSession ?? false
50
+ });
51
+ },
52
+ async completeChatCompletionPacket(request) {
53
+ const bindingStore = request.persistSession
54
+ ? sessionBindingStore
55
+ : createInMemorySessionBindingStore();
56
+ const providerBindingBefore = await bindingStore.loadBinding(request.providerId, request.sessionId);
57
+ try {
58
+ const completion = await completeValidatedChatCompletionPacket(request, providerBindingBefore, {
59
+ mode: "complete",
60
+ transport
61
+ });
62
+ if (completion.nextBinding) {
63
+ await bindingStore.saveBinding(request.providerId, request.sessionId, completion.nextBinding);
64
+ }
65
+ return {
66
+ packet: completion.packet,
67
+ providerBindingReused: providerBindingBefore !== null
68
+ };
69
+ }
70
+ catch (error) {
71
+ throw classifyChatCompletionPacketFailure(error, request, providerBindingBefore !== null);
72
+ }
73
+ },
74
+ async streamChatCompletionPacket(request) {
75
+ const bindingStore = request.persistSession
76
+ ? sessionBindingStore
77
+ : createInMemorySessionBindingStore();
78
+ const providerBindingBefore = await bindingStore.loadBinding(request.providerId, request.sessionId);
79
+ const providerBindingReused = providerBindingBefore !== null;
80
+ try {
81
+ const completion = await completeValidatedChatCompletionPacket(request, providerBindingBefore, {
82
+ mode: isStreamingProviderTransport(transport) ? "stream" : "complete",
83
+ transport
84
+ });
85
+ if (completion.nextBinding) {
86
+ await bindingStore.saveBinding(request.providerId, request.sessionId, completion.nextBinding);
87
+ }
88
+ return {
89
+ providerBindingReused,
90
+ packet: Promise.resolve(completion.packet),
91
+ content: singleProviderFragmentStream(serializeChatCompletionPacket(completion.packet))
92
+ };
93
+ }
94
+ catch (error) {
95
+ throw classifyChatCompletionPacketFailure(error, request, providerBindingReused);
96
+ }
97
+ },
98
+ async streamChatCompletion(request) {
99
+ const normalized = {
100
+ sessionId: request.sessionId,
101
+ input: request.input,
102
+ providerId: request.providerId,
103
+ modelId: request.modelId,
104
+ metadata: request.metadata,
105
+ toolProfile: request.toolProfile ?? "default",
106
+ sessionHistory: request.sessionHistory ?? [],
107
+ persistSession: request.persistSession ?? false
108
+ };
109
+ if (isStreamingProviderTransport(transport)) {
110
+ const bindingStore = normalized.persistSession
111
+ ? sessionBindingStore
112
+ : createInMemorySessionBindingStore();
113
+ try {
114
+ const providerBindingBefore = await bindingStore.loadBinding(normalized.providerId, normalized.sessionId);
115
+ const toolExecutor = new InProcessToolExecutor({
116
+ tools: createToolSet(normalized.toolProfile, dependencies.config.runtimeRoot)
117
+ });
118
+ const availableTools = await toolExecutor.getAvailableTools();
119
+ const compiled = compileProviderTurn({
120
+ conversation: {
121
+ sessionHistory: normalized.sessionHistory,
122
+ entries: [
123
+ {
124
+ type: "user_message",
125
+ content: normalized.input
126
+ }
127
+ ]
128
+ },
129
+ availableTools,
130
+ runtimePlannerPrimed: providerBindingBefore?.runtimePlannerPrimed === true,
131
+ forceReplay: false
132
+ });
133
+ const stream = await transport.streamChat({
134
+ lane: "main",
135
+ providerId: normalized.providerId,
136
+ modelId: normalized.modelId,
137
+ sessionId: normalized.sessionId,
138
+ requestId: crypto.randomUUID(),
139
+ attempt: 1,
140
+ continuation: compiled.summary.turnType === "follow_up",
141
+ toolFollowUp: false,
142
+ providerSessionReused: providerBindingBefore !== null,
143
+ messages: compiled.messages,
144
+ upstreamBinding: providerBindingBefore
145
+ ? {
146
+ conversationId: providerBindingBefore.conversationId,
147
+ parentId: providerBindingBefore.parentId
148
+ }
149
+ : null
150
+ });
151
+ return (async function* () {
152
+ let rawOutput = "";
153
+ let emittedOutput = "";
154
+ for await (const chunk of stream.content) {
155
+ rawOutput += chunk.content;
156
+ const visibleContent = extractIncrementalPacketMessage(rawOutput);
157
+ if (visibleContent.startsWith(emittedOutput) &&
158
+ visibleContent.length > emittedOutput.length) {
159
+ const delta = visibleContent.slice(emittedOutput.length);
160
+ emittedOutput = visibleContent;
161
+ yield delta;
162
+ }
163
+ }
164
+ const upstreamBinding = await stream.upstreamBinding;
165
+ if (upstreamBinding) {
166
+ await bindingStore.saveBinding(normalized.providerId, normalized.sessionId, {
167
+ ...upstreamBinding,
168
+ runtimePlannerPrimed: Boolean(upstreamBinding.parentId)
169
+ });
170
+ }
171
+ const output = sanitizeBridgeApiOutput(rawOutput).content;
172
+ if (output.startsWith(emittedOutput) && output.length > emittedOutput.length) {
173
+ yield output.slice(emittedOutput.length);
174
+ }
175
+ else if (!emittedOutput && output) {
176
+ yield output;
177
+ }
178
+ if (normalized.persistSession && output) {
179
+ await stateStore.appendSessionTurn(normalized.sessionId, {
180
+ userMessage: normalized.input,
181
+ assistantMessage: output,
182
+ assistantMode: "final"
183
+ });
184
+ }
185
+ })();
186
+ }
187
+ catch (error) {
188
+ throw classifyProviderFailure(serializeProviderFailure(classifyProviderTransportError(error)), {
189
+ sessionId: normalized.sessionId,
190
+ provider: {
191
+ id: normalized.providerId,
192
+ model: normalized.modelId
193
+ },
194
+ steps: 1,
195
+ tools: {
196
+ used: [],
197
+ calls: []
198
+ },
199
+ recovery: {
200
+ softRetryCount: 0,
201
+ providerSessionResetCount: 0
202
+ }
203
+ });
204
+ }
205
+ }
206
+ const response = await executeBridgeRequest(normalized);
207
+ return singleChunkStream(response.output);
208
+ }
209
+ };
210
+ async function executeBridgeRequest(request) {
211
+ const requestId = crypto.randomUUID();
212
+ const bindingStore = request.persistSession
213
+ ? sessionBindingStore
214
+ : createInMemorySessionBindingStore();
215
+ const providerBindingBefore = await bindingStore.loadBinding(request.providerId, request.sessionId);
216
+ const recoverySummary = {
217
+ softRetryCount: 0,
218
+ providerSessionResetCount: 0,
219
+ repair: createInitialRepairRecoverySummary()
220
+ };
221
+ emitLog({
222
+ scope: "request",
223
+ event: "bridge_request_started",
224
+ requestId,
225
+ detail: {
226
+ bridgeSessionId: request.sessionId,
227
+ providerId: request.providerId,
228
+ modelId: request.modelId,
229
+ sessionHistoryTurns: request.sessionHistory.length,
230
+ providerBindingReused: providerBindingBefore !== null
231
+ }
232
+ });
233
+ const provider = new SessionBoundProviderAdapter({
234
+ providerId: request.providerId,
235
+ modelId: request.modelId,
236
+ sessionId: request.sessionId,
237
+ bridgeRequestId: requestId,
238
+ sessionBindingStore: bindingStore,
239
+ transport,
240
+ onTraceEvent(type, detail) {
241
+ const record = asRecord(detail);
242
+ if (record.outcome === "soft_retry") {
243
+ recoverySummary.softRetryCount += 1;
244
+ }
245
+ if (type === "provider_session_reset") {
246
+ recoverySummary.providerSessionResetCount += 1;
247
+ }
248
+ emitLog({
249
+ scope: "provider",
250
+ event: type,
251
+ requestId,
252
+ detail: record
253
+ });
254
+ }
255
+ });
256
+ const toolExecutor = new InProcessToolExecutor({
257
+ tools: createToolSet(request.toolProfile, dependencies.config.runtimeRoot)
258
+ });
259
+ const outcome = await runBridgeRuntime({
260
+ userMessage: request.input,
261
+ sessionHistory: request.sessionHistory,
262
+ provider,
263
+ toolExecutor,
264
+ config: {
265
+ maxSteps: dependencies.config.maxSteps,
266
+ onEvent(event) {
267
+ updateRepairRecoverySummary(recoverySummary.repair, event);
268
+ emitLog({
269
+ scope: "runtime",
270
+ event: event.type,
271
+ requestId,
272
+ detail: summarizeRuntimeEvent(event)
273
+ });
274
+ }
275
+ }
276
+ });
277
+ emitLog({
278
+ scope: "request",
279
+ event: "bridge_request_finished",
280
+ requestId,
281
+ detail: {
282
+ bridgeSessionId: request.sessionId,
283
+ providerId: request.providerId,
284
+ modelId: request.modelId,
285
+ outcomeMode: outcome.mode,
286
+ steps: outcome.steps,
287
+ recovery: recoverySummary
288
+ }
289
+ });
290
+ if (outcome.mode === "fail") {
291
+ throw classifyRuntimeFailure(request.sessionId, request.providerId, request.modelId, outcome, recoverySummary);
292
+ }
293
+ if (request.persistSession) {
294
+ await stateStore.appendSessionTurn(request.sessionId, {
295
+ userMessage: request.input,
296
+ assistantMessage: outcome.message,
297
+ assistantMode: outcome.mode
298
+ });
299
+ }
300
+ const successfulOutcome = outcome;
301
+ return buildBridgeMessageResponse({
302
+ sessionId: request.sessionId,
303
+ providerId: request.providerId,
304
+ modelId: request.modelId,
305
+ metadata: request.metadata
306
+ }, successfulOutcome, providerBindingBefore !== null, recoverySummary);
307
+ }
308
+ }
309
+ function isStreamingProviderTransport(transport) {
310
+ return "streamChat" in transport && typeof transport.streamChat === "function";
311
+ }
312
+ async function* singleChunkStream(content) {
313
+ if (content) {
314
+ yield content;
315
+ }
316
+ }
317
+ async function* singleProviderFragmentStream(content) {
318
+ if (content) {
319
+ yield {
320
+ content,
321
+ responseId: "",
322
+ conversationId: "",
323
+ eventCountDelta: 0,
324
+ fragmentCountDelta: 1
325
+ };
326
+ }
327
+ }
328
+ function serializeChatCompletionPacket(packet) {
329
+ return serializeAssistantResponse(packet);
330
+ }
331
+ async function completeValidatedChatCompletionPacket(request, providerBindingBefore, options) {
332
+ let binding = providerBindingBefore;
333
+ let currentRequest = request;
334
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
335
+ const response = await executeChatCompletionTransportAttempt(currentRequest, binding, attempt, attempt === 1 ? options.mode : "complete", options.transport);
336
+ const nextBinding = response.upstreamBinding
337
+ ? {
338
+ ...response.upstreamBinding,
339
+ runtimePlannerPrimed: binding?.runtimePlannerPrimed
340
+ }
341
+ : binding;
342
+ try {
343
+ return {
344
+ packet: validateChatCompletionPacket(response.content, currentRequest.providerId, currentRequest.toolFollowUp, currentRequest.tools, currentRequest.toolChoice),
345
+ nextBinding
346
+ };
347
+ }
348
+ catch (error) {
349
+ const repairHint = buildChatCompletionRepairHint(error);
350
+ if (!repairHint || attempt === 3) {
351
+ throw error;
352
+ }
353
+ currentRequest = {
354
+ ...request,
355
+ messages: buildChatCompletionRepairMessages(request.messages, repairHint)
356
+ };
357
+ binding = nextBinding;
358
+ }
359
+ }
360
+ throw new Error("unreachable");
361
+ }
362
+ async function executeChatCompletionTransportAttempt(request, upstreamBinding, attempt, mode, transport) {
363
+ const baseRequest = {
364
+ lane: "main",
365
+ providerId: request.providerId,
366
+ modelId: request.modelId,
367
+ sessionId: request.sessionId,
368
+ requestId: crypto.randomUUID(),
369
+ attempt,
370
+ continuation: request.continuation,
371
+ toolFollowUp: request.toolFollowUp,
372
+ providerSessionReused: upstreamBinding !== null,
373
+ messages: request.messages,
374
+ upstreamBinding: upstreamBinding
375
+ ? {
376
+ conversationId: upstreamBinding.conversationId,
377
+ parentId: upstreamBinding.parentId
378
+ }
379
+ : null
380
+ };
381
+ if (mode === "stream" && isStreamingProviderTransport(transport)) {
382
+ const stream = await transport.streamChat(baseRequest);
383
+ let content = "";
384
+ for await (const chunk of stream.content) {
385
+ content += chunk.content;
386
+ }
387
+ return {
388
+ content,
389
+ upstreamBinding: await stream.upstreamBinding
390
+ };
391
+ }
392
+ return await transport.completeChat(baseRequest);
393
+ }
394
+ function normalizeBridgeRequest(request, pathSessionId, config, loadProvider) {
395
+ if (!isRecord(request)) {
396
+ throw new BridgeApiError({
397
+ statusCode: 400,
398
+ code: "invalid_request",
399
+ message: "Request body must be a JSON object."
400
+ });
401
+ }
402
+ const bodySessionId = optionalTrimmedString(request.sessionId, "sessionId");
403
+ if (pathSessionId && bodySessionId && pathSessionId !== bodySessionId) {
404
+ throw new BridgeApiError({
405
+ statusCode: 400,
406
+ code: "invalid_request",
407
+ message: "sessionId in the request body must match the sessionId path parameter."
408
+ });
409
+ }
410
+ const sessionId = pathSessionId ?? bodySessionId;
411
+ if (!sessionId) {
412
+ throw new BridgeApiError({
413
+ statusCode: 400,
414
+ code: "invalid_request",
415
+ message: "sessionId is required."
416
+ });
417
+ }
418
+ const input = resolveInput(request);
419
+ const providerId = optionalTrimmedString(request.provider, "provider") ?? config.defaultProvider;
420
+ if (!providerId) {
421
+ throw new BridgeApiError({
422
+ statusCode: 400,
423
+ code: "provider_required",
424
+ message: "provider is required when BRIDGE_PROVIDER is not configured."
425
+ });
426
+ }
427
+ const modelId = optionalTrimmedString(request.model, "model") ??
428
+ config.defaultModel ??
429
+ defaultModelForProvider(loadProvider?.(providerId) ?? null);
430
+ if (!modelId) {
431
+ throw new BridgeApiError({
432
+ statusCode: 400,
433
+ code: "model_required",
434
+ message: "model is required when BRIDGE_MODEL is not configured."
435
+ });
436
+ }
437
+ const metadata = normalizeMetadata(request.metadata);
438
+ const toolProfile = normalizeToolProfile(request.toolProfile);
439
+ return {
440
+ sessionId,
441
+ input,
442
+ providerId,
443
+ modelId,
444
+ metadata,
445
+ toolProfile
446
+ };
447
+ }
448
+ function resolveInput(request) {
449
+ const input = optionalTrimmedString(request.input, "input");
450
+ const message = optionalTrimmedString(request.message, "message");
451
+ if (input && message && input !== message) {
452
+ throw new BridgeApiError({
453
+ statusCode: 400,
454
+ code: "invalid_request",
455
+ message: "input and message must match when both are provided."
456
+ });
457
+ }
458
+ const resolved = input ?? message;
459
+ if (!resolved) {
460
+ throw new BridgeApiError({
461
+ statusCode: 400,
462
+ code: "invalid_request",
463
+ message: "input is required."
464
+ });
465
+ }
466
+ return resolved;
467
+ }
468
+ function normalizeMetadata(value) {
469
+ if (value === undefined) {
470
+ return undefined;
471
+ }
472
+ if (isRecord(value)) {
473
+ return value;
474
+ }
475
+ throw new BridgeApiError({
476
+ statusCode: 400,
477
+ code: "invalid_request",
478
+ message: "metadata must be a JSON object."
479
+ });
480
+ }
481
+ function normalizeToolProfile(value) {
482
+ if (value === undefined) {
483
+ return "default";
484
+ }
485
+ if (value === "default" || value === "workspace") {
486
+ return value;
487
+ }
488
+ throw new BridgeApiError({
489
+ statusCode: 400,
490
+ code: "invalid_request",
491
+ message: 'toolProfile must be either "default" or "workspace".'
492
+ });
493
+ }
494
+ function createToolSet(toolProfile, runtimeRoot) {
495
+ const defaultTools = createDefaultRuntimeTools(runtimeRoot);
496
+ if (toolProfile !== "workspace") {
497
+ return defaultTools;
498
+ }
499
+ return [...defaultTools, ...createSecondaryRuntimeTools(runtimeRoot)];
500
+ }
501
+ function classifyRuntimeFailure(sessionId, providerId, modelId, outcome, recoverySummary) {
502
+ const toolCalls = outcome.conversation.entries
503
+ .filter((entry) => entry.type === "tool_result")
504
+ .map((entry) => ({
505
+ id: entry.result.id,
506
+ name: entry.result.name,
507
+ ok: entry.result.ok
508
+ }));
509
+ const details = {
510
+ sessionId,
511
+ provider: {
512
+ id: providerId,
513
+ model: modelId
514
+ },
515
+ steps: outcome.steps,
516
+ tools: {
517
+ used: [...new Set(toolCalls.map((call) => call.name))],
518
+ calls: toolCalls
519
+ },
520
+ recovery: recoverySummary
521
+ };
522
+ if (outcome.failure?.source === "provider") {
523
+ return classifyProviderFailure(outcome.failure.provider, details);
524
+ }
525
+ if (outcome.failure?.source === "protocol") {
526
+ return new BridgeApiError({
527
+ statusCode: 502,
528
+ code: "provider_protocol_failure",
529
+ message: outcome.failure.message,
530
+ details: {
531
+ ...details,
532
+ failure: {
533
+ source: "protocol",
534
+ code: outcome.failure.code
535
+ }
536
+ }
537
+ });
538
+ }
539
+ if (outcome.failure?.source === "runtime" && outcome.failure.code === "max_steps_exhausted") {
540
+ return new BridgeApiError({
541
+ statusCode: 502,
542
+ code: "runtime_exhausted",
543
+ message: outcome.failure.message,
544
+ details: {
545
+ ...details,
546
+ failure: {
547
+ source: "runtime",
548
+ code: outcome.failure.code
549
+ }
550
+ }
551
+ });
552
+ }
553
+ if (toolCalls.some((call) => call.ok === false)) {
554
+ return new BridgeApiError({
555
+ statusCode: 502,
556
+ code: "tool_failure",
557
+ message: outcome.message,
558
+ details
559
+ });
560
+ }
561
+ return new BridgeApiError({
562
+ statusCode: 502,
563
+ code: "runtime_failure",
564
+ message: outcome.message,
565
+ details
566
+ });
567
+ }
568
+ function buildBridgeMessageResponse(request, outcome, providerBindingReused, recoverySummary) {
569
+ const sanitized = sanitizeBridgeApiOutput(outcome.message);
570
+ const toolCalls = outcome.conversation.entries
571
+ .filter((entry) => entry.type === "tool_result")
572
+ .map((entry) => ({
573
+ id: entry.result.id,
574
+ name: entry.result.name,
575
+ ok: entry.result.ok,
576
+ payload: entry.result.payload
577
+ }));
578
+ const lastSuccessfulCall = [...toolCalls].reverse().find((call) => call.ok);
579
+ return {
580
+ sessionId: request.sessionId,
581
+ output: sanitized.content,
582
+ outcome: {
583
+ mode: outcome.mode,
584
+ steps: outcome.steps
585
+ },
586
+ provider: {
587
+ id: request.providerId,
588
+ model: request.modelId
589
+ },
590
+ session: {
591
+ providerBindingReused
592
+ },
593
+ tools: {
594
+ used: [...new Set(toolCalls.map((call) => call.name))],
595
+ calls: toolCalls.map((call) => ({
596
+ id: call.id,
597
+ name: call.name,
598
+ ok: call.ok
599
+ })),
600
+ lastSuccessfulCall: lastSuccessfulCall
601
+ ? {
602
+ id: lastSuccessfulCall.id,
603
+ name: lastSuccessfulCall.name,
604
+ payload: lastSuccessfulCall.payload
605
+ }
606
+ : undefined
607
+ },
608
+ meta: {
609
+ outputSanitized: sanitized.sanitized,
610
+ sanitizationReason: sanitized.sanitized ? sanitized.reason : undefined,
611
+ requestMetadata: request.metadata,
612
+ recovery: recoverySummary
613
+ }
614
+ };
615
+ }
616
+ function classifyProviderFailure(failure, details) {
617
+ const errorDetails = {
618
+ ...details,
619
+ failure
620
+ };
621
+ const safeFailureMessage = formatSafeProviderFailureMessage(failure);
622
+ switch (failure.code) {
623
+ case "transport_timeout":
624
+ return new BridgeApiError({
625
+ statusCode: 504,
626
+ code: "provider_timeout",
627
+ message: safeFailureMessage,
628
+ details: errorDetails
629
+ });
630
+ case "empty_response":
631
+ case "empty_extracted_response":
632
+ case "empty_final_message":
633
+ return new BridgeApiError({
634
+ statusCode: 502,
635
+ code: "provider_empty_response",
636
+ message: safeFailureMessage,
637
+ details: errorDetails
638
+ });
639
+ case "packet_extraction_failed":
640
+ case "packet_normalization_failed":
641
+ case "packet_validation_failed":
642
+ return new BridgeApiError({
643
+ statusCode: 502,
644
+ code: "provider_protocol_failure",
645
+ message: safeFailureMessage,
646
+ details: errorDetails
647
+ });
648
+ case "authentication_failed":
649
+ return new BridgeApiError({
650
+ statusCode: 502,
651
+ code: "provider_auth_failure",
652
+ message: safeFailureMessage,
653
+ details: errorDetails
654
+ });
655
+ case "request_invalid":
656
+ case "unsupported_request":
657
+ return new BridgeApiError({
658
+ statusCode: 400,
659
+ code: "provider_request_failure",
660
+ message: safeFailureMessage,
661
+ details: errorDetails
662
+ });
663
+ case "session_reset_failed":
664
+ return new BridgeApiError({
665
+ statusCode: 502,
666
+ code: "provider_session_reset_failed",
667
+ message: safeFailureMessage,
668
+ details: errorDetails
669
+ });
670
+ case "transport_error":
671
+ default:
672
+ return new BridgeApiError({
673
+ statusCode: 502,
674
+ code: "provider_failure",
675
+ message: safeFailureMessage,
676
+ details: errorDetails
677
+ });
678
+ }
679
+ }
680
+ function formatSafeProviderFailureMessage(failure) {
681
+ if (failure.code === "transport_error") {
682
+ const details = failure.details;
683
+ if (details &&
684
+ typeof details === "object" &&
685
+ !Array.isArray(details) &&
686
+ typeof details.stage === "string" &&
687
+ typeof details.httpStatus === "number") {
688
+ return `Provider request failed during ${details.stage} with HTTP ${details.httpStatus}${formatProviderRecoverySummary(failure.recovery)}.`;
689
+ }
690
+ }
691
+ return failure.message;
692
+ }
693
+ function formatProviderRecoverySummary(recovery) {
694
+ const parts = [];
695
+ if (recovery.softRetryCount > 0) {
696
+ parts.push(`${recovery.softRetryCount} soft retr${recovery.softRetryCount === 1 ? "y" : "ies"}`);
697
+ }
698
+ if (recovery.sessionResetCount > 0) {
699
+ parts.push(`${recovery.sessionResetCount} provider-session reset${recovery.sessionResetCount === 1 ? "" : "s"}`);
700
+ }
701
+ if (parts.length === 0) {
702
+ return "";
703
+ }
704
+ return ` after ${parts.join(" and ")}`;
705
+ }
706
+ function validateChatCompletionPacket(content, providerId, allowVisibleTextFinal, tools, toolChoice) {
707
+ if (!content.trim()) {
708
+ throw new ProviderFailure({
709
+ kind: "transient",
710
+ code: "empty_response",
711
+ message: "Provider returned an empty response.",
712
+ retryable: true,
713
+ sessionResetEligible: false,
714
+ emptyOutput: true
715
+ });
716
+ }
717
+ try {
718
+ const packet = parseToolAwareAssistantResponse(providerId, content, allowVisibleTextFinal);
719
+ validateChatCompletionAssistantResponse(packet, tools, toolChoice);
720
+ return packet;
721
+ }
722
+ catch (error) {
723
+ if (error instanceof ProviderFailure) {
724
+ throw error;
725
+ }
726
+ throw new ProviderFailure({
727
+ kind: "protocol",
728
+ code: "packet_validation_failed",
729
+ message: `Invalid assistant response: ${error instanceof Error ? error.message : String(error)}`,
730
+ displayMessage: "Provider returned malformed or unusable output.",
731
+ retryable: false,
732
+ sessionResetEligible: false,
733
+ cause: error
734
+ });
735
+ }
736
+ }
737
+ function parseToolAwareAssistantResponse(providerId, content, allowVisibleTextFinal) {
738
+ const repairedSimplePacket = repairMalformedSimpleAssistantPacket(content);
739
+ try {
740
+ return normalizeAssistantResponseToolNames(parseAssistantResponse(repairedSimplePacket));
741
+ }
742
+ catch (assistantProtocolError) {
743
+ const canonicalPacket = coerceMalformedCanonicalPacket(content) ??
744
+ coerceToolAwareProviderOutput(content) ??
745
+ (allowVisibleTextFinal ? wrapVisibleTextAsFinalPacket(content) : null) ??
746
+ normalizeExtractedProviderPacket(providerId, content);
747
+ if (!canonicalPacket) {
748
+ throw assistantProtocolError;
749
+ }
750
+ const packet = parseZcPacket(canonicalPacket);
751
+ switch (packet.mode) {
752
+ case "final":
753
+ return {
754
+ type: "final",
755
+ message: packet.message
756
+ };
757
+ case "tool_request":
758
+ return {
759
+ type: "tool",
760
+ toolCall: {
761
+ id: packet.toolCall.id,
762
+ name: normalizeProviderToolName(packet.toolCall.name),
763
+ arguments: packet.toolCall.args
764
+ }
765
+ };
766
+ case "ask_user":
767
+ case "fail":
768
+ throw new Error(`Packet mode "${packet.mode}" is not supported for OpenAI-style chat completions.`);
769
+ default:
770
+ throw new Error("Unsupported packet mode.");
771
+ }
772
+ }
773
+ }
774
+ function normalizeAssistantResponseToolNames(packet) {
775
+ if (packet.type !== "tool") {
776
+ return packet;
777
+ }
778
+ return {
779
+ ...packet,
780
+ toolCall: {
781
+ ...packet.toolCall,
782
+ name: normalizeProviderToolName(packet.toolCall.name)
783
+ }
784
+ };
785
+ }
786
+ function repairMalformedSimpleAssistantPacket(content) {
787
+ const trimmed = content.trim();
788
+ if (/^<tool>[\s\S]*<\/tool_call>$/u.test(trimmed) && !trimmed.includes("</tool>")) {
789
+ return trimmed.replace(/<\/tool_call>$/u, "</tool>");
790
+ }
791
+ const repairedLeadingBlock = extractLeadingSimpleFinalBlock(trimmed);
792
+ if (repairedLeadingBlock) {
793
+ return repairedLeadingBlock;
794
+ }
795
+ return trimmed;
796
+ }
797
+ function extractLeadingSimpleFinalBlock(content) {
798
+ const openTag = "<final>";
799
+ const closeTag = "</final>";
800
+ if (!content.startsWith(openTag)) {
801
+ return null;
802
+ }
803
+ const closeIndex = content.indexOf(closeTag);
804
+ if (closeIndex < 0) {
805
+ return null;
806
+ }
807
+ const block = content.slice(0, closeIndex + closeTag.length);
808
+ const trailing = content.slice(closeIndex + closeTag.length).trim();
809
+ if (!trailing) {
810
+ return block;
811
+ }
812
+ return trailing.startsWith("<") ? null : block;
813
+ }
814
+ function validateChatCompletionAssistantResponse(packet, tools, toolChoice) {
815
+ if (packet.type === "final") {
816
+ return;
817
+ }
818
+ if (toolChoice === "none") {
819
+ throw new ProviderFailure({
820
+ kind: "permanent",
821
+ code: "unsupported_request",
822
+ message: "Provider requested a tool call even though tool calls are disabled for this request.",
823
+ displayMessage: "Provider requested a disabled tool call.",
824
+ retryable: false,
825
+ sessionResetEligible: false,
826
+ details: {
827
+ toolName: packet.toolCall.name
828
+ }
829
+ });
830
+ }
831
+ if (typeof toolChoice === "object" && packet.toolCall.name !== toolChoice.function.name) {
832
+ throw new ProviderFailure({
833
+ kind: "permanent",
834
+ code: "request_invalid",
835
+ message: `Provider requested tool "${packet.toolCall.name}" but only "${toolChoice.function.name}" is allowed for this request.`,
836
+ displayMessage: `Provider requested unavailable tool "${packet.toolCall.name}".`,
837
+ retryable: false,
838
+ sessionResetEligible: false,
839
+ details: {
840
+ toolName: packet.toolCall.name,
841
+ allowedToolNames: [toolChoice.function.name]
842
+ }
843
+ });
844
+ }
845
+ const tool = tools.find((candidate) => candidate.function.name === packet.toolCall.name);
846
+ if (!tool) {
847
+ throw new ProviderFailure({
848
+ kind: "permanent",
849
+ code: "request_invalid",
850
+ message: `Provider requested tool "${packet.toolCall.name}" but it is not present in the bridge tool manifest.`,
851
+ displayMessage: `Provider requested unavailable tool "${packet.toolCall.name}".`,
852
+ retryable: false,
853
+ sessionResetEligible: false,
854
+ details: {
855
+ toolName: packet.toolCall.name,
856
+ availableToolNames: tools.map((candidate) => candidate.function.name)
857
+ }
858
+ });
859
+ }
860
+ const schema = isRecord(tool.function.parameters) ? tool.function.parameters : null;
861
+ if (!schema) {
862
+ return;
863
+ }
864
+ const validationError = validateJsonSchemaValue(packet.toolCall.arguments, schema, "arguments");
865
+ if (validationError) {
866
+ throw new Error(validationError);
867
+ }
868
+ }
869
+ function buildChatCompletionRepairHint(error) {
870
+ if (!(error instanceof ProviderFailure)) {
871
+ return null;
872
+ }
873
+ if (error.code !== "packet_validation_failed") {
874
+ return null;
875
+ }
876
+ return [
877
+ "Protocol error.",
878
+ "",
879
+ "You must answer using exactly one of these formats:",
880
+ "",
881
+ "<final>",
882
+ "your text here",
883
+ "</final>",
884
+ "",
885
+ "<tool>",
886
+ '{"name":"tool_name","arguments":{}}',
887
+ "</tool>",
888
+ "",
889
+ "Rules:",
890
+ "- no markdown",
891
+ "- no backticks",
892
+ "- no extra text",
893
+ "- exactly one block only",
894
+ "- if using <tool>, arguments must be valid JSON",
895
+ "",
896
+ "Re-emit your previous intent now."
897
+ ].join(" ");
898
+ }
899
+ function buildChatCompletionRepairMessages(messages, repairHint) {
900
+ return [
901
+ ...messages,
902
+ {
903
+ role: "user",
904
+ content: repairHint
905
+ }
906
+ ];
907
+ }
908
+ function classifyChatCompletionPacketFailure(error, request, providerBindingReused) {
909
+ return classifyProviderFailure(serializeProviderFailure(classifyProviderTransportError(error)), {
910
+ sessionId: request.sessionId,
911
+ provider: {
912
+ id: request.providerId,
913
+ model: request.modelId
914
+ },
915
+ continuation: request.continuation,
916
+ toolFollowUp: request.toolFollowUp,
917
+ providerBindingReused,
918
+ requestMetadata: request.metadata
919
+ });
920
+ }
921
+ function coerceToolAwareProviderOutput(content) {
922
+ return (wrapLooseZcPacketAsCanonicalPacket(content) ??
923
+ wrapBareToolCallAsCanonicalPacket(content) ??
924
+ wrapBareFinalLikeMessageAsCanonicalPacket(content));
925
+ }
926
+ function coerceMalformedCanonicalPacket(content) {
927
+ const trimmed = content.trim();
928
+ const rootMatch = trimmed.match(/^<zc_packet version="1">([\s\S]*)<\/zc_packet>$/u);
929
+ if (!rootMatch) {
930
+ return null;
931
+ }
932
+ const rootBody = rootMatch[1] ?? "";
933
+ const modeMatch = rootBody.match(/<mode>(final|tool_request|ask_user|fail)<\/mode>/u);
934
+ if (!modeMatch?.[1]) {
935
+ return null;
936
+ }
937
+ const mode = modeMatch[1];
938
+ const remainder = rootBody.replace(modeMatch[0], "");
939
+ if (mode === "tool_request") {
940
+ const parsedToolCall = parseLooseToolCall(remainder);
941
+ if (!parsedToolCall) {
942
+ return null;
943
+ }
944
+ return createToolRequestPacket(parsedToolCall);
945
+ }
946
+ const messageMatch = remainder.match(/<message>([\s\S]*?)<\/message>/u);
947
+ if (!messageMatch) {
948
+ return null;
949
+ }
950
+ return createMessagePacket(mode, parseLenientCanonicalMessage(messageMatch[1] ?? ""));
951
+ }
952
+ function parseLenientCanonicalMessage(raw) {
953
+ const trimmed = raw.trim();
954
+ if (!trimmed) {
955
+ return "";
956
+ }
957
+ if (trimmed.startsWith("<![CDATA[")) {
958
+ const cdataEnd = trimmed.indexOf("]]>");
959
+ if (cdataEnd >= 0) {
960
+ return trimmed.slice("<![CDATA[".length, cdataEnd).trim();
961
+ }
962
+ }
963
+ return trimmed;
964
+ }
965
+ function wrapLooseZcPacketAsCanonicalPacket(content) {
966
+ const trimmed = content.trim();
967
+ const match = trimmed.match(/^<zc_packet\b([^>]*)>([\s\S]*)<\/zc_packet>$/u);
968
+ if (!match) {
969
+ return null;
970
+ }
971
+ const attributes = match[1] ?? "";
972
+ if (/\bversion="1"/u.test(attributes)) {
973
+ return null;
974
+ }
975
+ const modeMatch = attributes.match(/\bmode="(final|tool_request|ask_user|fail)"/u);
976
+ if (!modeMatch) {
977
+ return null;
978
+ }
979
+ const mode = modeMatch[1];
980
+ const body = (match[2] ?? "").trim();
981
+ if (!body) {
982
+ return null;
983
+ }
984
+ if (mode === "tool_request") {
985
+ return wrapBareToolCallAsCanonicalPacket(body);
986
+ }
987
+ const message = parseLooseMessageBody(body);
988
+ return message ? createMessagePacket(mode, message) : null;
989
+ }
990
+ function wrapBareToolCallAsCanonicalPacket(content) {
991
+ const trimmed = repairMalformedBareToolCall(content.trim());
992
+ if (!trimmed || !/^<tool_call\b[\s\S]*<\/tool_call>$/u.test(trimmed)) {
993
+ const parsedToolCall = parseLooseToolCall(content);
994
+ return parsedToolCall ? createToolRequestPacket(parsedToolCall) : null;
995
+ }
996
+ return `<zc_packet version="1"><mode>tool_request</mode>${trimmed}</zc_packet>`;
997
+ }
998
+ function repairMalformedBareToolCall(trimmed) {
999
+ if (/^<tool_call\b[\s\S]*<\/tool_call>$/u.test(trimmed)) {
1000
+ return trimmed;
1001
+ }
1002
+ if (/^<tool_call\b[\s\S]*<tool_call>\s*$/u.test(trimmed) && !trimmed.includes("</tool_call>")) {
1003
+ return trimmed.replace(/<tool_call>\s*$/u, "</tool_call>");
1004
+ }
1005
+ return null;
1006
+ }
1007
+ function parseLooseToolCall(content) {
1008
+ const toolMatch = content.match(/<tool_call\b([^>]*)>([\s\S]*?)<\/tool_call>/u);
1009
+ if (!toolMatch) {
1010
+ return null;
1011
+ }
1012
+ const attributes = parseLooseXmlAttributes(toolMatch[1] ?? "");
1013
+ const rawBody = (toolMatch[2] ?? "").trim();
1014
+ const parsedJsonArguments = parseLooseToolArguments(rawBody);
1015
+ const parsedTaggedToolCall = parseTaggedToolCallBody(rawBody);
1016
+ const name = (attributes.name ?? "").trim() || parsedTaggedToolCall?.name || "";
1017
+ const id = (attributes.id ?? "").trim() || "call_1";
1018
+ const args = parsedJsonArguments ?? parsedTaggedToolCall?.args;
1019
+ if (!name || !args) {
1020
+ return null;
1021
+ }
1022
+ return {
1023
+ id,
1024
+ name,
1025
+ args
1026
+ };
1027
+ }
1028
+ function parseLooseXmlAttributes(source) {
1029
+ const attributes = {};
1030
+ for (const match of source.matchAll(/\b([A-Za-z_][\w:.-]*)=(?:"([^"]*)"|'([^']*)')/g)) {
1031
+ const key = match[1]?.trim();
1032
+ if (!key) {
1033
+ continue;
1034
+ }
1035
+ attributes[key] = decodeXmlEntityValue((match[2] ?? match[3] ?? "").trim());
1036
+ }
1037
+ return attributes;
1038
+ }
1039
+ function parseLooseToolArguments(raw) {
1040
+ const stripped = stripCodeFence(raw.trim());
1041
+ if (!stripped) {
1042
+ return null;
1043
+ }
1044
+ try {
1045
+ const parsed = JSON.parse(stripped);
1046
+ return isRecord(parsed) ? parsed : null;
1047
+ }
1048
+ catch {
1049
+ return null;
1050
+ }
1051
+ }
1052
+ function parseTaggedToolCallBody(raw) {
1053
+ const functionMatch = raw.match(/<function=(?:"([^"]+)"|'([^']+)'|([^>\s]+))>([\s\S]*?)<\/function>/u);
1054
+ const functionName = decodeXmlEntityValue((functionMatch?.[1] ?? functionMatch?.[2] ?? functionMatch?.[3] ?? "").trim());
1055
+ const functionBody = (functionMatch?.[4] ?? "").trim();
1056
+ if (!functionName || !functionBody) {
1057
+ return null;
1058
+ }
1059
+ const args = {};
1060
+ for (const match of functionBody.matchAll(/<parameter=(?:"([^"]+)"|'([^']+)'|([^>\s]+))>([\s\S]*?)<\/parameter>/g)) {
1061
+ const key = decodeXmlEntityValue((match[1] ?? match[2] ?? match[3] ?? "").trim());
1062
+ if (!key) {
1063
+ continue;
1064
+ }
1065
+ const value = parseLooseParameterValue(match[4] ?? "");
1066
+ if (value === undefined) {
1067
+ continue;
1068
+ }
1069
+ args[key] = value;
1070
+ }
1071
+ return Object.keys(args).length > 0
1072
+ ? {
1073
+ name: functionName,
1074
+ args
1075
+ }
1076
+ : null;
1077
+ }
1078
+ function parseLooseParameterValue(raw) {
1079
+ const trimmed = raw.trim();
1080
+ if (!trimmed) {
1081
+ return "";
1082
+ }
1083
+ const parsedJson = parseLooseToolArguments(trimmed);
1084
+ if (parsedJson) {
1085
+ return parsedJson;
1086
+ }
1087
+ return decodeXmlEntityValue(trimmed);
1088
+ }
1089
+ function stripCodeFence(raw) {
1090
+ const fencedMatch = raw.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/u);
1091
+ return fencedMatch?.[1]?.trim() ?? raw;
1092
+ }
1093
+ function decodeXmlEntityValue(value) {
1094
+ return value
1095
+ .replaceAll("&quot;", '"')
1096
+ .replaceAll("&apos;", "'")
1097
+ .replaceAll("&lt;", "<")
1098
+ .replaceAll("&gt;", ">")
1099
+ .replaceAll("&amp;", "&");
1100
+ }
1101
+ function wrapBareFinalLikeMessageAsCanonicalPacket(content) {
1102
+ const trimmed = content.trim();
1103
+ const match = trimmed.match(/^<(final|ask_user|fail)>([\s\S]*)<\/\1>$/u);
1104
+ if (!match) {
1105
+ return null;
1106
+ }
1107
+ const mode = match[1];
1108
+ const message = parseLooseMessageBody(match[2] ?? "");
1109
+ return message ? createMessagePacket(mode, message) : null;
1110
+ }
1111
+ function parseLooseMessageBody(raw) {
1112
+ const trimmed = raw.trim();
1113
+ if (!trimmed) {
1114
+ return null;
1115
+ }
1116
+ if (trimmed.startsWith("<![CDATA[") && trimmed.endsWith("]]>")) {
1117
+ return trimmed.slice("<![CDATA[".length, -"]]>".length).trim();
1118
+ }
1119
+ if (trimmed.includes("<")) {
1120
+ return null;
1121
+ }
1122
+ return trimmed;
1123
+ }
1124
+ function wrapVisibleTextAsFinalPacket(content) {
1125
+ const sanitized = sanitizeBridgeApiOutput(content);
1126
+ if (sanitized.sanitized || !sanitized.content.trim()) {
1127
+ return null;
1128
+ }
1129
+ return createMessagePacket("final", sanitized.content);
1130
+ }
1131
+ function normalizeExtractedProviderPacket(providerId, content) {
1132
+ const extracted = extractPacketCandidate(content);
1133
+ if (!extracted.ok) {
1134
+ return null;
1135
+ }
1136
+ const normalized = normalizeProviderPacket(providerId, extracted.packetText);
1137
+ return normalized.ok ? normalized.canonicalPacket : null;
1138
+ }
1139
+ function validateJsonSchemaValue(value, schema, path) {
1140
+ if (Array.isArray(schema.enum) &&
1141
+ schema.enum.length > 0 &&
1142
+ !schema.enum.some((candidate) => Object.is(candidate, value))) {
1143
+ return `${path} must be one of the allowed enum values.`;
1144
+ }
1145
+ if ("const" in schema && !Object.is(schema.const, value)) {
1146
+ return `${path} must match the required constant value.`;
1147
+ }
1148
+ const schemaType = typeof schema.type === "string" ? schema.type : null;
1149
+ if (!schemaType) {
1150
+ return null;
1151
+ }
1152
+ switch (schemaType) {
1153
+ case "object": {
1154
+ if (!isRecord(value)) {
1155
+ return `${path} must be an object.`;
1156
+ }
1157
+ const properties = isRecord(schema.properties) ? schema.properties : {};
1158
+ const required = Array.isArray(schema.required)
1159
+ ? schema.required.filter((entry) => typeof entry === "string" && entry.length > 0)
1160
+ : [];
1161
+ const additionalProperties = schema.additionalProperties;
1162
+ for (const key of required) {
1163
+ if (!(key in value)) {
1164
+ return `${path}.${key} is required.`;
1165
+ }
1166
+ }
1167
+ for (const [key, childValue] of Object.entries(value)) {
1168
+ const childSchema = properties[key];
1169
+ if (isRecord(childSchema)) {
1170
+ const childError = validateJsonSchemaValue(childValue, childSchema, `${path}.${key}`);
1171
+ if (childError) {
1172
+ return childError;
1173
+ }
1174
+ continue;
1175
+ }
1176
+ if (additionalProperties === false) {
1177
+ return `${path}.${key} is not allowed.`;
1178
+ }
1179
+ if (isRecord(additionalProperties)) {
1180
+ const childError = validateJsonSchemaValue(childValue, additionalProperties, `${path}.${key}`);
1181
+ if (childError) {
1182
+ return childError;
1183
+ }
1184
+ }
1185
+ }
1186
+ return null;
1187
+ }
1188
+ case "array": {
1189
+ if (!Array.isArray(value)) {
1190
+ return `${path} must be an array.`;
1191
+ }
1192
+ if (isRecord(schema.items)) {
1193
+ for (let index = 0; index < value.length; index += 1) {
1194
+ const childError = validateJsonSchemaValue(value[index], schema.items, `${path}[${index}]`);
1195
+ if (childError) {
1196
+ return childError;
1197
+ }
1198
+ }
1199
+ }
1200
+ return null;
1201
+ }
1202
+ case "string":
1203
+ return typeof value === "string" ? null : `${path} must be a string.`;
1204
+ case "boolean":
1205
+ return typeof value === "boolean" ? null : `${path} must be a boolean.`;
1206
+ case "number":
1207
+ return typeof value === "number" && Number.isFinite(value)
1208
+ ? null
1209
+ : `${path} must be a number.`;
1210
+ case "integer":
1211
+ return typeof value === "number" && Number.isInteger(value)
1212
+ ? null
1213
+ : `${path} must be an integer.`;
1214
+ case "null":
1215
+ return value === null ? null : `${path} must be null.`;
1216
+ default:
1217
+ return null;
1218
+ }
1219
+ }
1220
+ function optionalTrimmedString(value, key) {
1221
+ if (value === undefined) {
1222
+ return null;
1223
+ }
1224
+ if (typeof value !== "string") {
1225
+ throw new BridgeApiError({
1226
+ statusCode: 400,
1227
+ code: "invalid_request",
1228
+ message: `${key} must be a string.`
1229
+ });
1230
+ }
1231
+ const trimmed = value.trim();
1232
+ if (!trimmed) {
1233
+ throw new BridgeApiError({
1234
+ statusCode: 400,
1235
+ code: "invalid_request",
1236
+ message: `${key} must be a non-empty string.`
1237
+ });
1238
+ }
1239
+ return trimmed;
1240
+ }
1241
+ function isRecord(value) {
1242
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1243
+ }
1244
+ function asRecord(value) {
1245
+ return isRecord(value) ? value : {};
1246
+ }
1247
+ function summarizeRuntimeEvent(event) {
1248
+ const type = typeof event.type === "string" ? event.type : "unknown";
1249
+ switch (type) {
1250
+ case "provider_response":
1251
+ return {
1252
+ type,
1253
+ step: event.step,
1254
+ durationMs: event.durationMs,
1255
+ rawTextLength: typeof event.rawText === "string" ? event.rawText.length : 0
1256
+ };
1257
+ case "tool_result": {
1258
+ const result = asRecord(event.result);
1259
+ return {
1260
+ type,
1261
+ step: event.step,
1262
+ durationMs: event.durationMs,
1263
+ tool: {
1264
+ id: result.id,
1265
+ name: result.name,
1266
+ ok: result.ok
1267
+ }
1268
+ };
1269
+ }
1270
+ case "packet_parsed":
1271
+ return {
1272
+ type,
1273
+ step: event.step,
1274
+ mode: event.mode
1275
+ };
1276
+ case "packet_parse_failed":
1277
+ return {
1278
+ type,
1279
+ step: event.step,
1280
+ error: event.error
1281
+ };
1282
+ case "main_response_invalid":
1283
+ return {
1284
+ type,
1285
+ step: event.step,
1286
+ error: event.error,
1287
+ rawTextLength: event.rawTextLength
1288
+ };
1289
+ case "repair_attempted":
1290
+ return {
1291
+ type,
1292
+ step: event.step
1293
+ };
1294
+ case "repair_valid":
1295
+ return {
1296
+ type,
1297
+ step: event.step,
1298
+ mode: event.mode,
1299
+ rawTextLength: event.rawTextLength
1300
+ };
1301
+ case "repair_failed":
1302
+ return {
1303
+ type,
1304
+ step: event.step,
1305
+ reason: event.reason,
1306
+ error: event.error,
1307
+ providerFailure: event.providerFailure
1308
+ };
1309
+ case "outcome": {
1310
+ const outcome = asRecord(event.outcome);
1311
+ const failure = asRecord(outcome.failure);
1312
+ return {
1313
+ type,
1314
+ outcome: {
1315
+ mode: outcome.mode,
1316
+ steps: outcome.steps,
1317
+ failureSource: failure.source
1318
+ }
1319
+ };
1320
+ }
1321
+ default:
1322
+ return event;
1323
+ }
1324
+ }
1325
+ function createInitialRepairRecoverySummary() {
1326
+ return {
1327
+ attempted: false,
1328
+ attemptCount: 0,
1329
+ outcome: "not_needed",
1330
+ invalidCount: 0
1331
+ };
1332
+ }
1333
+ function updateRepairRecoverySummary(summary, event) {
1334
+ const type = typeof event.type === "string" ? event.type : "";
1335
+ switch (type) {
1336
+ case "main_response_invalid":
1337
+ summary.invalidCount += 1;
1338
+ break;
1339
+ case "repair_attempted":
1340
+ summary.attempted = true;
1341
+ summary.attemptCount = 1;
1342
+ if (summary.outcome === "not_needed") {
1343
+ summary.outcome = "failed";
1344
+ }
1345
+ break;
1346
+ case "repair_valid":
1347
+ summary.attempted = true;
1348
+ summary.attemptCount = 1;
1349
+ summary.outcome = "valid";
1350
+ delete summary.failureReason;
1351
+ break;
1352
+ case "repair_failed":
1353
+ summary.attempted = true;
1354
+ summary.attemptCount = 1;
1355
+ summary.outcome = "failed";
1356
+ if (event.reason === "provider_failure" || event.reason === "protocol_invalid") {
1357
+ summary.failureReason = event.reason;
1358
+ }
1359
+ break;
1360
+ default:
1361
+ break;
1362
+ }
1363
+ }
1364
+ function createInMemorySessionBindingStore() {
1365
+ const bindings = new Map();
1366
+ return {
1367
+ async loadBinding(providerId, sessionId) {
1368
+ return bindings.get(createBindingKey(providerId, sessionId)) ?? null;
1369
+ },
1370
+ async saveBinding(providerId, sessionId, binding) {
1371
+ bindings.set(createBindingKey(providerId, sessionId), structuredClone(binding));
1372
+ },
1373
+ async clearBinding(providerId, sessionId) {
1374
+ bindings.delete(createBindingKey(providerId, sessionId));
1375
+ }
1376
+ };
1377
+ }
1378
+ function createBindingKey(providerId, sessionId) {
1379
+ return `${providerId}:${sessionId}`;
1380
+ }
1381
+ function defaultLogEvent(event) {
1382
+ console.log(`[BridgeService][${event.scope}] ${event.event} ${JSON.stringify({ requestId: event.requestId, ...event.detail })}`);
1383
+ }
1384
+ export const bridgeRuntimeServiceModule = {
1385
+ createBridgeRuntimeService
1386
+ };