@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,47 @@
|
|
|
1
|
+
import { normalizersModule } from "./normalizers/index.js";
|
|
2
|
+
const { normalizeGenericLegacyPacket } = normalizersModule;
|
|
3
|
+
function normalizeProviderPacket(_providerId, extractedCandidate) {
|
|
4
|
+
const candidate = extractedCandidate.trim();
|
|
5
|
+
if (candidate.startsWith('<zc_packet version="1">')) {
|
|
6
|
+
const repairedCanonicalPacket = repairMalformedCanonicalPacket(candidate);
|
|
7
|
+
if (repairedCanonicalPacket) {
|
|
8
|
+
return {
|
|
9
|
+
ok: true,
|
|
10
|
+
strategy: "canonical_repaired_tool_call_close_tag",
|
|
11
|
+
canonicalPacket: repairedCanonicalPacket,
|
|
12
|
+
notes: ["Repaired a malformed canonical </tool_call> closing tag before strict parsing."]
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
ok: true,
|
|
17
|
+
strategy: "canonical_passthrough",
|
|
18
|
+
canonicalPacket: candidate,
|
|
19
|
+
notes: ["Candidate already matched the canonical runtime packet format."]
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const genericLegacyPacket = normalizeGenericLegacyPacket(candidate);
|
|
23
|
+
if (genericLegacyPacket) {
|
|
24
|
+
return genericLegacyPacket;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
ok: false,
|
|
28
|
+
strategy: "none",
|
|
29
|
+
code: "unsupported_provider_packet",
|
|
30
|
+
message: "Non-canonical packet content did not match the supported legacy packet format.",
|
|
31
|
+
notes: ["Only canonical passthrough and generic legacy packet normalization are available."]
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function repairMalformedCanonicalPacket(candidate) {
|
|
35
|
+
const attributedOpenTagMatches = [...candidate.matchAll(/<tool_call\b[^>]+>/g)];
|
|
36
|
+
const closeTagMatches = [...candidate.matchAll(/<\/tool_call>/g)];
|
|
37
|
+
const hasTrailingBareOpenTag = /<tool_call>\s*<\/zc_packet>\s*$/u.test(candidate);
|
|
38
|
+
if (attributedOpenTagMatches.length !== 1 ||
|
|
39
|
+
closeTagMatches.length !== 0 ||
|
|
40
|
+
!hasTrailingBareOpenTag) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return candidate.replace(/<tool_call>\s*<\/zc_packet>\s*$/u, "</tool_call></zc_packet>");
|
|
44
|
+
}
|
|
45
|
+
export const packetNormalizerModule = {
|
|
46
|
+
normalizeProviderPacket
|
|
47
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ToolDefinition } from "./execution/types.ts";
|
|
2
|
+
import type { ConversationState } from "./provider.ts";
|
|
3
|
+
type CompiledProviderMessage = {
|
|
4
|
+
role: "system" | "user";
|
|
5
|
+
content: string;
|
|
6
|
+
};
|
|
7
|
+
type CompiledProviderTurn = {
|
|
8
|
+
messages: CompiledProviderMessage[];
|
|
9
|
+
summary: {
|
|
10
|
+
turnType: "initial" | "follow_up";
|
|
11
|
+
userMessage: string;
|
|
12
|
+
toolNames: string[];
|
|
13
|
+
toolResultCount: number;
|
|
14
|
+
sessionHistoryTurns: number;
|
|
15
|
+
replayedFromBridgeSession: boolean;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
type CompileProviderTurnInput = {
|
|
19
|
+
conversation: ConversationState;
|
|
20
|
+
availableTools: ToolDefinition[];
|
|
21
|
+
runtimePlannerPrimed?: boolean;
|
|
22
|
+
forceReplay?: boolean;
|
|
23
|
+
};
|
|
24
|
+
declare function compileProviderTurn(input: CompileProviderTurnInput): CompiledProviderTurn;
|
|
25
|
+
export declare const promptCompilerModule: {
|
|
26
|
+
compileProviderTurn: typeof compileProviderTurn;
|
|
27
|
+
};
|
|
28
|
+
export type { CompiledProviderMessage, CompiledProviderTurn, CompileProviderTurnInput };
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
function compileProviderTurn(input) {
|
|
2
|
+
const userMessage = getUserMessage(input.conversation);
|
|
3
|
+
const toolResults = input.conversation.entries.filter((entry) => entry.type === "tool_result");
|
|
4
|
+
const toolNames = [...input.availableTools].map((tool) => tool.name).sort();
|
|
5
|
+
const sessionHistory = input.conversation.sessionHistory ?? [];
|
|
6
|
+
const shouldReplayBridgeSession = input.forceReplay === true ||
|
|
7
|
+
(sessionHistory.length > 0 && input.runtimePlannerPrimed !== true);
|
|
8
|
+
if (toolResults.length === 0) {
|
|
9
|
+
if (shouldReplayBridgeSession) {
|
|
10
|
+
return {
|
|
11
|
+
messages: [
|
|
12
|
+
{
|
|
13
|
+
role: "system",
|
|
14
|
+
content: buildSystemPrompt(input.availableTools)
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
role: "user",
|
|
18
|
+
content: buildBridgeSessionReplayPrompt(sessionHistory, userMessage)
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
summary: {
|
|
22
|
+
turnType: sessionHistory.length > 0 ? "follow_up" : "initial",
|
|
23
|
+
userMessage,
|
|
24
|
+
toolNames,
|
|
25
|
+
toolResultCount: 0,
|
|
26
|
+
sessionHistoryTurns: sessionHistory.length,
|
|
27
|
+
replayedFromBridgeSession: true
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const systemPrompt = input.runtimePlannerPrimed
|
|
32
|
+
? buildCompactSystemPrompt(input.availableTools)
|
|
33
|
+
: buildSystemPrompt(input.availableTools);
|
|
34
|
+
return {
|
|
35
|
+
messages: [
|
|
36
|
+
{
|
|
37
|
+
role: "system",
|
|
38
|
+
content: systemPrompt
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
role: "user",
|
|
42
|
+
content: userMessage
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
summary: {
|
|
46
|
+
turnType: "initial",
|
|
47
|
+
userMessage,
|
|
48
|
+
toolNames,
|
|
49
|
+
toolResultCount: 0,
|
|
50
|
+
sessionHistoryTurns: sessionHistory.length,
|
|
51
|
+
replayedFromBridgeSession: false
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (shouldReplayBridgeSession) {
|
|
56
|
+
return {
|
|
57
|
+
messages: [
|
|
58
|
+
{
|
|
59
|
+
role: "system",
|
|
60
|
+
content: buildSystemPrompt(input.availableTools)
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
role: "user",
|
|
64
|
+
content: buildBridgeSessionToolReplayPrompt(sessionHistory, userMessage, input.availableTools, toolResults)
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
summary: {
|
|
68
|
+
turnType: "follow_up",
|
|
69
|
+
userMessage,
|
|
70
|
+
toolNames,
|
|
71
|
+
toolResultCount: toolResults.length,
|
|
72
|
+
sessionHistoryTurns: sessionHistory.length,
|
|
73
|
+
replayedFromBridgeSession: true
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
messages: [
|
|
79
|
+
{
|
|
80
|
+
role: "user",
|
|
81
|
+
content: buildToolFollowUpPrompt(userMessage, input.availableTools, toolResults)
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
summary: {
|
|
85
|
+
turnType: "follow_up",
|
|
86
|
+
userMessage,
|
|
87
|
+
toolNames,
|
|
88
|
+
toolResultCount: toolResults.length,
|
|
89
|
+
sessionHistoryTurns: sessionHistory.length,
|
|
90
|
+
replayedFromBridgeSession: false
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function buildSystemPrompt(availableTools) {
|
|
95
|
+
const manifest = [...availableTools]
|
|
96
|
+
.sort((left, right) => left.name.localeCompare(right.name))
|
|
97
|
+
.map((tool) => renderToolDefinition(tool))
|
|
98
|
+
.join("\n\n");
|
|
99
|
+
const toolNames = new Set(availableTools.map((tool) => tool.name));
|
|
100
|
+
const validToolRequestExample = '<tool>{"name":"read","arguments":{"path":"package.json"}}</tool>';
|
|
101
|
+
const validEditExample = '<tool>{"name":"edit","arguments":{"path":"src/app.ts","oldText":"const enabled = false;","newText":"const enabled = true;"}}</tool>';
|
|
102
|
+
const validWriteExample = '<tool>{"name":"write","arguments":{"path":"notes/output.txt","content":"hello from runtime"}}</tool>';
|
|
103
|
+
const validBashExample = '<tool>{"name":"bash","arguments":{"command":"git status --short"}}</tool>';
|
|
104
|
+
const validFinalExample = "<final>The package name is example-app and the version is 0.1.0.</final>";
|
|
105
|
+
const validExamples = [
|
|
106
|
+
...(toolNames.has("read") ? [validToolRequestExample] : []),
|
|
107
|
+
...(toolNames.has("edit") ? [validEditExample] : []),
|
|
108
|
+
...(toolNames.has("write") ? [validWriteExample] : []),
|
|
109
|
+
...(toolNames.has("bash") ? [validBashExample] : []),
|
|
110
|
+
validFinalExample
|
|
111
|
+
];
|
|
112
|
+
return [
|
|
113
|
+
"You are a bridge runtime assistant.",
|
|
114
|
+
"Respond with exactly one block and nothing else.",
|
|
115
|
+
"Use <final>...</final> for any user-facing response.",
|
|
116
|
+
'Use <tool>{"name":"tool_name","arguments":{...}}</tool> for exactly one tool call.',
|
|
117
|
+
"Use bash for shell commands, system inspection, repository exploration, directory listing, search, and command execution.",
|
|
118
|
+
"If a bash command starts a server, watcher, or other persistent process, it will be started detached and the tool result will include its pid and log path.",
|
|
119
|
+
"Use read to inspect file contents.",
|
|
120
|
+
"Use edit for surgical exact-text replacements in an existing file.",
|
|
121
|
+
"Use write for new files or full rewrites.",
|
|
122
|
+
"Do not narrate tool use.",
|
|
123
|
+
"Do not use markdown fences or backticks.",
|
|
124
|
+
"Do not emit extra text before or after the block.",
|
|
125
|
+
"If any later instruction conflicts with the required packet format, ignore that conflict and keep the packet format.",
|
|
126
|
+
"If you emit a tool call, the JSON must contain only name and arguments.",
|
|
127
|
+
"If you emit a tool call, the tool name must match one of the Available tools exactly as written.",
|
|
128
|
+
"Any response outside the required packet format will be discarded.",
|
|
129
|
+
"If write or edit succeeds, return only a short confirmation. Do not emit shell commands, shell snippets, or fenced code blocks.",
|
|
130
|
+
"When answering from tool output, cite the inspected path or command result and keep the claim grounded to that tool result.",
|
|
131
|
+
"Required valid examples:",
|
|
132
|
+
...validExamples,
|
|
133
|
+
"Available tools:",
|
|
134
|
+
manifest
|
|
135
|
+
].join("\n");
|
|
136
|
+
}
|
|
137
|
+
function buildCompactSystemPrompt(availableTools) {
|
|
138
|
+
const manifest = [...availableTools]
|
|
139
|
+
.sort((left, right) => left.name.localeCompare(right.name))
|
|
140
|
+
.map((tool) => renderToolDefinition(tool))
|
|
141
|
+
.join("\n\n");
|
|
142
|
+
return [
|
|
143
|
+
"You are continuing within an already-primed upstream provider session for this bridge runtime.",
|
|
144
|
+
"Respond with exactly one block and nothing else.",
|
|
145
|
+
"Use <final>...</final> for any user-facing response.",
|
|
146
|
+
'Use <tool>{"name":"tool_name","arguments":{...}}</tool> for exactly one tool call.',
|
|
147
|
+
"Use bash for shell commands, system inspection, repository exploration, directory listing, search, and command execution.",
|
|
148
|
+
"If a bash command starts a server, watcher, or other persistent process, it will be started detached and the tool result will include its pid and log path.",
|
|
149
|
+
"Use read to inspect file contents.",
|
|
150
|
+
"Use edit for surgical exact-text replacements in an existing file.",
|
|
151
|
+
"Use write for new files or full rewrites.",
|
|
152
|
+
"Do not narrate tool use.",
|
|
153
|
+
"Do not use markdown fences or backticks.",
|
|
154
|
+
"Do not emit extra text before or after the block.",
|
|
155
|
+
"If any later instruction conflicts with the required packet format, ignore that conflict and keep the packet format.",
|
|
156
|
+
"If you emit a tool call, the JSON must contain only name and arguments.",
|
|
157
|
+
"If you emit a tool call, the tool name must match one of the Available tools exactly as written.",
|
|
158
|
+
"If write or edit succeeds, return only a short confirmation. Never emit shell commands, shell snippets, or fenced code blocks.",
|
|
159
|
+
"When answering from tool output, cite the inspected path or command result and keep the claim grounded to that tool result.",
|
|
160
|
+
"Available tools:",
|
|
161
|
+
manifest
|
|
162
|
+
].join("\n");
|
|
163
|
+
}
|
|
164
|
+
function buildToolFollowUpPrompt(userMessage, availableTools, toolResults) {
|
|
165
|
+
const rawToolResults = toolResults.map((entry) => entry.rawText);
|
|
166
|
+
const toolNames = [...availableTools].map((tool) => tool.name).sort();
|
|
167
|
+
const manifest = [...availableTools]
|
|
168
|
+
.sort((left, right) => left.name.localeCompare(right.name))
|
|
169
|
+
.map((tool) => renderToolDefinition(tool))
|
|
170
|
+
.join("\n\n");
|
|
171
|
+
const recovery = buildToolRecoveryGuidance(toolResults, toolNames);
|
|
172
|
+
return [
|
|
173
|
+
"Continue the same task using the tool result below.",
|
|
174
|
+
"Prefer bash for command execution, system inspection, repository exploration, directory listing, and search.",
|
|
175
|
+
"Long-running bash commands start detached; use the returned pid or log path to inspect them in later steps.",
|
|
176
|
+
"Prefer read for file inspection, edit for exact-text replacements, and write for full rewrites.",
|
|
177
|
+
"If write or edit succeeded, the final response must be a short confirmation only. Never emit shell commands, shell snippets, or fenced code blocks.",
|
|
178
|
+
"When answering from tool output, cite the inspected path or command result and keep the claim grounded to that tool result.",
|
|
179
|
+
...(recovery ? [recovery] : []),
|
|
180
|
+
`Available tool names:\n${toolNames.map((name) => `- ${name}`).join("\n")}`,
|
|
181
|
+
"Available tools:",
|
|
182
|
+
manifest,
|
|
183
|
+
`Original user request:\n${userMessage}`,
|
|
184
|
+
"Tool results:",
|
|
185
|
+
rawToolResults.join("\n"),
|
|
186
|
+
buildProtocolFooter()
|
|
187
|
+
].join("\n\n");
|
|
188
|
+
}
|
|
189
|
+
function buildBridgeSessionReplayPrompt(sessionHistory, userMessage) {
|
|
190
|
+
return [
|
|
191
|
+
"Resume this bridge session from the durable bridge-owned conversation history below.",
|
|
192
|
+
"Treat the previous turns as authoritative context for the same logical bridge session.",
|
|
193
|
+
"Previous bridge turns:",
|
|
194
|
+
renderBridgeSessionHistory(sessionHistory),
|
|
195
|
+
"Current user request:",
|
|
196
|
+
userMessage,
|
|
197
|
+
buildProtocolFooter()
|
|
198
|
+
].join("\n\n");
|
|
199
|
+
}
|
|
200
|
+
function buildBridgeSessionToolReplayPrompt(sessionHistory, userMessage, availableTools, toolResults) {
|
|
201
|
+
const rawToolResults = toolResults.map((entry) => entry.rawText);
|
|
202
|
+
const toolNames = [...availableTools].map((tool) => tool.name).sort();
|
|
203
|
+
const manifest = [...availableTools]
|
|
204
|
+
.sort((left, right) => left.name.localeCompare(right.name))
|
|
205
|
+
.map((tool) => renderToolDefinition(tool))
|
|
206
|
+
.join("\n\n");
|
|
207
|
+
const recovery = buildToolRecoveryGuidance(toolResults, toolNames);
|
|
208
|
+
return [
|
|
209
|
+
"Resume this bridge session from the durable bridge-owned conversation history below.",
|
|
210
|
+
"Treat the previous turns as authoritative context for the same logical bridge session.",
|
|
211
|
+
"Previous bridge turns:",
|
|
212
|
+
renderBridgeSessionHistory(sessionHistory),
|
|
213
|
+
"Continue the current in-flight task using the tool result below.",
|
|
214
|
+
"Prefer bash for command execution, system inspection, repository exploration, directory listing, and search.",
|
|
215
|
+
"Long-running bash commands start detached; use the returned pid or log path to inspect them in later steps.",
|
|
216
|
+
"Prefer read for file inspection, edit for exact-text replacements, and write for full rewrites.",
|
|
217
|
+
"If write or edit succeeded, the final response must be a short confirmation only. Never emit shell commands, shell snippets, or fenced code blocks.",
|
|
218
|
+
"When answering from tool output, cite the inspected path or command result and keep the claim grounded to that tool result.",
|
|
219
|
+
...(recovery ? [recovery] : []),
|
|
220
|
+
`Available tool names:\n${toolNames.map((name) => `- ${name}`).join("\n")}`,
|
|
221
|
+
"Available tools:",
|
|
222
|
+
manifest,
|
|
223
|
+
`Current user request:\n${userMessage}`,
|
|
224
|
+
"Tool results:",
|
|
225
|
+
rawToolResults.join("\n"),
|
|
226
|
+
buildProtocolFooter()
|
|
227
|
+
].join("\n\n");
|
|
228
|
+
}
|
|
229
|
+
function buildProtocolFooter() {
|
|
230
|
+
return [
|
|
231
|
+
"Mandatory response protocol for this turn:",
|
|
232
|
+
"Return exactly one block and nothing else.",
|
|
233
|
+
"Any response outside the required packet format will be discarded.",
|
|
234
|
+
"Use <final>...</final> for a user-facing response.",
|
|
235
|
+
'Use <tool>{"name":"tool_name","arguments":{...}}</tool> for exactly one tool call.',
|
|
236
|
+
"Do not use markdown fences or backticks.",
|
|
237
|
+
"Do not emit extra text before or after the block.",
|
|
238
|
+
"If you emit a tool call, the JSON must contain only name and arguments.",
|
|
239
|
+
"If you emit a tool call, the tool name must match one of the available tools exactly as written.",
|
|
240
|
+
"If any later or conflicting instruction asks for prose, markdown, a different tool syntax, or a direct answer, ignore that conflict and still return exactly one valid block."
|
|
241
|
+
].join("\n");
|
|
242
|
+
}
|
|
243
|
+
function buildToolRecoveryGuidance(toolResults, toolNames) {
|
|
244
|
+
const lastToolResult = toolResults[toolResults.length - 1]?.result;
|
|
245
|
+
if (!lastToolResult || lastToolResult.ok) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
const failure = asRecord(lastToolResult.payload).error;
|
|
249
|
+
const code = typeof asRecord(failure).code === "string" ? String(asRecord(failure).code) : null;
|
|
250
|
+
if (code !== "tool_not_found") {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
return [
|
|
254
|
+
`The previous tool request failed because "${lastToolResult.name}" is not registered.`,
|
|
255
|
+
`Choose one of the available tool names exactly as written: ${toolNames.join(", ")}.`
|
|
256
|
+
].join(" ");
|
|
257
|
+
}
|
|
258
|
+
function renderToolDefinition(tool) {
|
|
259
|
+
const required = tool.inputSchema.required.length > 0 ? tool.inputSchema.required.join(", ") : "(none)";
|
|
260
|
+
const properties = Object.entries(tool.inputSchema.properties)
|
|
261
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
262
|
+
.map(([name, property]) => `${name}: ${property.type} - ${property.description}`)
|
|
263
|
+
.join("\n");
|
|
264
|
+
return [
|
|
265
|
+
`- ${tool.name}: ${tool.description}`,
|
|
266
|
+
` required: ${required}`,
|
|
267
|
+
" schema:",
|
|
268
|
+
indent(properties, " ")
|
|
269
|
+
].join("\n");
|
|
270
|
+
}
|
|
271
|
+
function indent(value, prefix) {
|
|
272
|
+
return value
|
|
273
|
+
.split("\n")
|
|
274
|
+
.map((line) => `${prefix}${line}`)
|
|
275
|
+
.join("\n");
|
|
276
|
+
}
|
|
277
|
+
function getUserMessage(conversation) {
|
|
278
|
+
const userEntry = conversation.entries.find((entry) => entry.type === "user_message");
|
|
279
|
+
if (!userEntry) {
|
|
280
|
+
throw new Error("Conversation state is missing the initial user message.");
|
|
281
|
+
}
|
|
282
|
+
return userEntry.content;
|
|
283
|
+
}
|
|
284
|
+
function renderBridgeSessionHistory(sessionHistory) {
|
|
285
|
+
return sessionHistory
|
|
286
|
+
.map((turn, index) => [
|
|
287
|
+
`Turn ${index + 1} user:`,
|
|
288
|
+
turn.userMessage,
|
|
289
|
+
`Turn ${index + 1} assistant (${turn.assistantMode}):`,
|
|
290
|
+
turn.assistantMessage
|
|
291
|
+
].join("\n"))
|
|
292
|
+
.join("\n\n");
|
|
293
|
+
}
|
|
294
|
+
function asRecord(value) {
|
|
295
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
296
|
+
? value
|
|
297
|
+
: {};
|
|
298
|
+
}
|
|
299
|
+
export const promptCompilerModule = {
|
|
300
|
+
compileProviderTurn
|
|
301
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
type PacketMode = "final" | "tool_request" | "ask_user" | "fail";
|
|
2
|
+
type ToolCall = {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
args: Record<string, unknown>;
|
|
6
|
+
};
|
|
7
|
+
type ToolResult = {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
ok: boolean;
|
|
11
|
+
payload: unknown;
|
|
12
|
+
};
|
|
13
|
+
type FinalPacket = {
|
|
14
|
+
mode: "final";
|
|
15
|
+
message: string;
|
|
16
|
+
};
|
|
17
|
+
type AskUserPacket = {
|
|
18
|
+
mode: "ask_user";
|
|
19
|
+
message: string;
|
|
20
|
+
};
|
|
21
|
+
type FailPacket = {
|
|
22
|
+
mode: "fail";
|
|
23
|
+
message: string;
|
|
24
|
+
};
|
|
25
|
+
type ToolRequestPacket = {
|
|
26
|
+
mode: "tool_request";
|
|
27
|
+
toolCall: ToolCall;
|
|
28
|
+
};
|
|
29
|
+
type ZcPacket = FinalPacket | AskUserPacket | FailPacket | ToolRequestPacket;
|
|
30
|
+
declare class PacketProtocolError extends Error {
|
|
31
|
+
constructor(message: string);
|
|
32
|
+
}
|
|
33
|
+
declare function parseZcPacket(rawText: string): ZcPacket;
|
|
34
|
+
declare function serializeToolResult(result: ToolResult): string;
|
|
35
|
+
declare function createToolRequestPacket(toolCall: ToolCall): string;
|
|
36
|
+
declare function createMessagePacket(mode: Extract<PacketMode, "final" | "ask_user" | "fail">, message: string): string;
|
|
37
|
+
export declare const protocolModule: {
|
|
38
|
+
PacketProtocolError: typeof PacketProtocolError;
|
|
39
|
+
parseZcPacket: typeof parseZcPacket;
|
|
40
|
+
serializeToolResult: typeof serializeToolResult;
|
|
41
|
+
createToolRequestPacket: typeof createToolRequestPacket;
|
|
42
|
+
createMessagePacket: typeof createMessagePacket;
|
|
43
|
+
};
|
|
44
|
+
export type { AskUserPacket, FailPacket, FinalPacket, PacketMode, PacketProtocolError, ToolCall, ToolRequestPacket, ToolResult, ZcPacket };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
class PacketProtocolError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "PacketProtocolError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
function parseZcPacket(rawText) {
|
|
8
|
+
const trimmed = rawText.trim();
|
|
9
|
+
const rootMatch = trimmed.match(/^<zc_packet version="1">([\s\S]*)<\/zc_packet>$/);
|
|
10
|
+
if (!rootMatch) {
|
|
11
|
+
throw new PacketProtocolError('Provider output must be exactly one <zc_packet version="1"> document.');
|
|
12
|
+
}
|
|
13
|
+
const rootBody = rootMatch[1] ?? "";
|
|
14
|
+
const modeNode = extractSingleTag(rootBody, "mode");
|
|
15
|
+
const mode = parseMode(modeNode.inner.trim());
|
|
16
|
+
if (mode === "tool_request") {
|
|
17
|
+
const toolNode = extractSingleToolCall(modeNode.remainder);
|
|
18
|
+
return {
|
|
19
|
+
mode,
|
|
20
|
+
toolCall: {
|
|
21
|
+
id: toolNode.id,
|
|
22
|
+
name: toolNode.name,
|
|
23
|
+
args: parseToolArguments(toolNode.inner)
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const messageNode = extractSingleTag(modeNode.remainder, "message");
|
|
28
|
+
return {
|
|
29
|
+
mode,
|
|
30
|
+
message: parseMessageContent(messageNode.inner)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function serializeToolResult(result) {
|
|
34
|
+
return [
|
|
35
|
+
`<tool_result version="1" id="${escapeAttribute(result.id)}" name="${escapeAttribute(result.name)}" ok="${result.ok ? "true" : "false"}">`,
|
|
36
|
+
JSON.stringify(result.payload),
|
|
37
|
+
"</tool_result>"
|
|
38
|
+
].join("");
|
|
39
|
+
}
|
|
40
|
+
function createToolRequestPacket(toolCall) {
|
|
41
|
+
return [
|
|
42
|
+
'<zc_packet version="1">',
|
|
43
|
+
"<mode>tool_request</mode>",
|
|
44
|
+
`<tool_call id="${escapeAttribute(toolCall.id)}" name="${escapeAttribute(toolCall.name)}">`,
|
|
45
|
+
JSON.stringify(toolCall.args),
|
|
46
|
+
"</tool_call>",
|
|
47
|
+
"</zc_packet>"
|
|
48
|
+
].join("");
|
|
49
|
+
}
|
|
50
|
+
function createMessagePacket(mode, message) {
|
|
51
|
+
return [
|
|
52
|
+
'<zc_packet version="1">',
|
|
53
|
+
`<mode>${mode}</mode>`,
|
|
54
|
+
`<message><![CDATA[${message}]]></message>`,
|
|
55
|
+
"</zc_packet>"
|
|
56
|
+
].join("");
|
|
57
|
+
}
|
|
58
|
+
function parseMode(value) {
|
|
59
|
+
switch (value) {
|
|
60
|
+
case "final":
|
|
61
|
+
case "tool_request":
|
|
62
|
+
case "ask_user":
|
|
63
|
+
case "fail":
|
|
64
|
+
return value;
|
|
65
|
+
default:
|
|
66
|
+
throw new PacketProtocolError(`Unsupported packet mode "${value || "(empty)"}".`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function parseToolArguments(raw) {
|
|
70
|
+
let parsed;
|
|
71
|
+
try {
|
|
72
|
+
parsed = JSON.parse(raw.trim());
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
throw new PacketProtocolError("tool_call body must contain valid JSON.");
|
|
76
|
+
}
|
|
77
|
+
if (!isRecord(parsed)) {
|
|
78
|
+
throw new PacketProtocolError("tool_call JSON must decode to an object.");
|
|
79
|
+
}
|
|
80
|
+
return parsed;
|
|
81
|
+
}
|
|
82
|
+
function parseMessageContent(raw) {
|
|
83
|
+
const trimmed = raw.trim();
|
|
84
|
+
if (trimmed.startsWith("<![CDATA[") && trimmed.endsWith("]]>")) {
|
|
85
|
+
return trimmed.slice("<![CDATA[".length, -"]]>".length).trim();
|
|
86
|
+
}
|
|
87
|
+
if (trimmed.includes("<")) {
|
|
88
|
+
throw new PacketProtocolError("message body must be plain text or a single CDATA section.");
|
|
89
|
+
}
|
|
90
|
+
return trimmed;
|
|
91
|
+
}
|
|
92
|
+
function extractSingleTag(source, tagName) {
|
|
93
|
+
const expression = new RegExp(`<${tagName}>([\\s\\S]*?)<\\/${tagName}>`, "g");
|
|
94
|
+
const matches = [...source.matchAll(expression)];
|
|
95
|
+
if (matches.length !== 1) {
|
|
96
|
+
throw new PacketProtocolError(`Expected exactly one <${tagName}> element.`);
|
|
97
|
+
}
|
|
98
|
+
const match = matches[0];
|
|
99
|
+
const index = match.index ?? 0;
|
|
100
|
+
const fullMatch = match[0];
|
|
101
|
+
const remainder = `${source.slice(0, index)}${source.slice(index + fullMatch.length)}`;
|
|
102
|
+
if (remainder.trim().length > 0 && tagName !== "mode") {
|
|
103
|
+
throw new PacketProtocolError(`Unexpected content alongside <${tagName}>.`);
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
inner: match[1] ?? "",
|
|
107
|
+
remainder
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function extractSingleToolCall(source) {
|
|
111
|
+
const expression = /<tool_call\b([^>]*)>([\s\S]*?)<\/tool_call>/g;
|
|
112
|
+
const matches = [...source.matchAll(expression)];
|
|
113
|
+
if (matches.length !== 1) {
|
|
114
|
+
throw new PacketProtocolError("Expected exactly one <tool_call> element.");
|
|
115
|
+
}
|
|
116
|
+
const match = matches[0];
|
|
117
|
+
const index = match.index ?? 0;
|
|
118
|
+
const fullMatch = match[0];
|
|
119
|
+
const remainder = `${source.slice(0, index)}${source.slice(index + fullMatch.length)}`;
|
|
120
|
+
if (remainder.trim().length > 0) {
|
|
121
|
+
throw new PacketProtocolError("Unexpected content alongside <tool_call>.");
|
|
122
|
+
}
|
|
123
|
+
const attributes = parseXmlAttributes(match[1] ?? "");
|
|
124
|
+
const id = (attributes.id ?? "").trim();
|
|
125
|
+
const name = (attributes.name ?? "").trim();
|
|
126
|
+
if (!id) {
|
|
127
|
+
throw new PacketProtocolError("tool_call id is required.");
|
|
128
|
+
}
|
|
129
|
+
if (!name) {
|
|
130
|
+
throw new PacketProtocolError("tool_call name is required.");
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
id,
|
|
134
|
+
name,
|
|
135
|
+
inner: match[2] ?? ""
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function parseXmlAttributes(source) {
|
|
139
|
+
const attributes = {};
|
|
140
|
+
for (const match of source.matchAll(/\b([A-Za-z_][\w:.-]*)="([^"]*)"/g)) {
|
|
141
|
+
const key = match[1]?.trim();
|
|
142
|
+
if (!key) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
attributes[key] = match[2] ?? "";
|
|
146
|
+
}
|
|
147
|
+
return attributes;
|
|
148
|
+
}
|
|
149
|
+
function escapeAttribute(value) {
|
|
150
|
+
return value
|
|
151
|
+
.replaceAll("&", "&")
|
|
152
|
+
.replaceAll('"', """)
|
|
153
|
+
.replaceAll("<", "<")
|
|
154
|
+
.replaceAll(">", ">");
|
|
155
|
+
}
|
|
156
|
+
function isRecord(value) {
|
|
157
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
158
|
+
}
|
|
159
|
+
export const protocolModule = {
|
|
160
|
+
PacketProtocolError,
|
|
161
|
+
parseZcPacket,
|
|
162
|
+
serializeToolResult,
|
|
163
|
+
createToolRequestPacket,
|
|
164
|
+
createMessagePacket
|
|
165
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
type ProviderFailureKind = "transient" | "session_corruption" | "protocol" | "permanent";
|
|
2
|
+
type ProviderFailureCode = "transport_timeout" | "transport_error" | "empty_response" | "empty_extracted_response" | "empty_final_message" | "packet_extraction_failed" | "packet_normalization_failed" | "packet_validation_failed" | "authentication_failed" | "request_invalid" | "unsupported_request" | "session_reset_failed";
|
|
3
|
+
type ProviderRecoveryState = {
|
|
4
|
+
softRetryCount: number;
|
|
5
|
+
sessionResetCount: number;
|
|
6
|
+
};
|
|
7
|
+
type SerializedProviderFailure = {
|
|
8
|
+
kind: ProviderFailureKind;
|
|
9
|
+
code: ProviderFailureCode;
|
|
10
|
+
message: string;
|
|
11
|
+
retryable: boolean;
|
|
12
|
+
sessionResetEligible: boolean;
|
|
13
|
+
emptyOutput: boolean;
|
|
14
|
+
recovery: ProviderRecoveryState;
|
|
15
|
+
details?: Record<string, unknown>;
|
|
16
|
+
};
|
|
17
|
+
declare class ProviderFailure extends Error {
|
|
18
|
+
readonly kind: ProviderFailureKind;
|
|
19
|
+
readonly code: ProviderFailureCode;
|
|
20
|
+
readonly displayMessage: string;
|
|
21
|
+
readonly retryable: boolean;
|
|
22
|
+
readonly sessionResetEligible: boolean;
|
|
23
|
+
readonly emptyOutput: boolean;
|
|
24
|
+
readonly recovery: ProviderRecoveryState;
|
|
25
|
+
readonly details: Record<string, unknown> | undefined;
|
|
26
|
+
constructor(input: {
|
|
27
|
+
kind: ProviderFailureKind;
|
|
28
|
+
code: ProviderFailureCode;
|
|
29
|
+
message: string;
|
|
30
|
+
displayMessage?: string;
|
|
31
|
+
retryable?: boolean;
|
|
32
|
+
sessionResetEligible?: boolean;
|
|
33
|
+
emptyOutput?: boolean;
|
|
34
|
+
recovery?: Partial<ProviderRecoveryState>;
|
|
35
|
+
details?: Record<string, unknown>;
|
|
36
|
+
cause?: unknown;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
declare function isProviderFailure(error: unknown): error is ProviderFailure;
|
|
40
|
+
declare function serializeProviderFailure(error: ProviderFailure): SerializedProviderFailure;
|
|
41
|
+
declare function withProviderRecovery(error: ProviderFailure, recovery: Partial<ProviderRecoveryState>, overrides?: Partial<Pick<ProviderFailure, "kind" | "displayMessage" | "retryable" | "sessionResetEligible">>): ProviderFailure;
|
|
42
|
+
declare function classifyProviderTransportError(error: unknown): ProviderFailure;
|
|
43
|
+
declare function formatProviderFailureMessage(error: ProviderFailure): string;
|
|
44
|
+
export declare const providerFailureModule: {
|
|
45
|
+
ProviderFailure: typeof ProviderFailure;
|
|
46
|
+
isProviderFailure: typeof isProviderFailure;
|
|
47
|
+
serializeProviderFailure: typeof serializeProviderFailure;
|
|
48
|
+
withProviderRecovery: typeof withProviderRecovery;
|
|
49
|
+
classifyProviderTransportError: typeof classifyProviderTransportError;
|
|
50
|
+
formatProviderFailureMessage: typeof formatProviderFailureMessage;
|
|
51
|
+
};
|
|
52
|
+
export type { ProviderFailure, ProviderFailureCode, ProviderFailureKind, ProviderRecoveryState, SerializedProviderFailure };
|