@superjackfruitlabs/salaam 0.0.1 → 0.0.2

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 (110) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +25 -17
  3. package/dist/src/a2a-backend.js +225 -0
  4. package/dist/src/a2a-inbound.js +487 -0
  5. package/dist/src/a2a-peer.js +370 -0
  6. package/dist/src/a2a-protocol.js +250 -0
  7. package/dist/src/agent-profiles.js +289 -0
  8. package/dist/src/children.js +132 -26
  9. package/dist/src/claude-provider.js +2 -179
  10. package/dist/src/cli-help.js +107 -6
  11. package/dist/src/cli.js +477 -13
  12. package/dist/src/config.js +26 -3
  13. package/dist/src/credentials.js +7 -2
  14. package/dist/src/external-runtime.js +1 -0
  15. package/dist/src/external-work.js +583 -0
  16. package/dist/src/harness-backend.js +466 -0
  17. package/dist/src/harness-container.js +450 -0
  18. package/dist/src/harness-model-gateway.js +104 -0
  19. package/dist/src/harness-prepare.js +88 -0
  20. package/dist/src/harness-probe.js +111 -0
  21. package/dist/src/harness-protocol.js +325 -0
  22. package/dist/src/harness-readiness.js +50 -0
  23. package/dist/src/harness-rpc.js +206 -0
  24. package/dist/src/harness-tool-identity.js +87 -0
  25. package/dist/src/harness-wire-bridge.js +387 -0
  26. package/dist/src/host.js +6 -1
  27. package/dist/src/house-extension.js +147 -64
  28. package/dist/src/live-provider.js +301 -0
  29. package/dist/src/managed.js +33 -3
  30. package/dist/src/protocol.js +9 -5
  31. package/dist/src/provider-auth.js +300 -0
  32. package/dist/src/provider-config.js +65 -17
  33. package/dist/src/provider-model.js +78 -0
  34. package/dist/src/provider-payload.js +264 -0
  35. package/dist/src/provider-transport.js +36 -0
  36. package/dist/src/provider-usage.js +59 -0
  37. package/dist/src/repository-snapshots.js +219 -0
  38. package/dist/src/service.js +274 -9
  39. package/dist/src/store.js +44 -2
  40. package/dist/src/supervisor.js +246 -35
  41. package/dist/src/task-budget.js +17 -8
  42. package/dist/src/task-context.js +15 -0
  43. package/dist/src/task-driver.js +38 -14
  44. package/dist/src/task-policy.js +20 -1
  45. package/dist/src/task-verification.js +3 -0
  46. package/dist/src/task-work.js +31 -0
  47. package/dist/src/team-work.js +378 -0
  48. package/dist/src/tui-view.js +65 -10
  49. package/dist/src/tui.js +4 -2
  50. package/dist/src/usage.js +18 -0
  51. package/dist/src/worker-boundary.js +298 -0
  52. package/dist/src/worker-workspaces.js +274 -0
  53. package/dist/src/workspaces.js +6 -2
  54. package/harness-runtime/codex-pins.json +10 -0
  55. package/harness-runtime/package-lock.json +560 -0
  56. package/harness-runtime/package.json +9 -0
  57. package/npm-shrinkwrap.json +57 -2
  58. package/package.json +2 -1
  59. package/src/a2a-backend.ts +254 -0
  60. package/src/a2a-inbound.ts +585 -0
  61. package/src/a2a-peer.ts +441 -0
  62. package/src/a2a-protocol.ts +286 -0
  63. package/src/agent-profiles.ts +379 -0
  64. package/src/children.ts +168 -27
  65. package/src/claude-provider.ts +2 -189
  66. package/src/cli-help.ts +107 -6
  67. package/src/cli.ts +602 -12
  68. package/src/config.ts +34 -3
  69. package/src/credentials.ts +9 -2
  70. package/src/external-runtime.ts +54 -0
  71. package/src/external-work.ts +663 -0
  72. package/src/harness-backend.ts +557 -0
  73. package/src/harness-container.ts +543 -0
  74. package/src/harness-model-gateway.ts +138 -0
  75. package/src/harness-prepare.ts +113 -0
  76. package/src/harness-probe.ts +150 -0
  77. package/src/harness-protocol.ts +388 -0
  78. package/src/harness-readiness.ts +71 -0
  79. package/src/harness-rpc.ts +244 -0
  80. package/src/harness-tool-identity.ts +91 -0
  81. package/src/harness-wire-bridge.ts +418 -0
  82. package/src/host.ts +11 -6
  83. package/src/house-extension.ts +165 -66
  84. package/src/live-provider.ts +347 -0
  85. package/src/managed.ts +43 -4
  86. package/src/protocol.ts +36 -8
  87. package/src/provider-auth.ts +336 -0
  88. package/src/provider-config.ts +89 -18
  89. package/src/provider-model.ts +86 -0
  90. package/src/provider-payload.ts +263 -0
  91. package/src/provider-transport.ts +51 -0
  92. package/src/provider-usage.ts +64 -0
  93. package/src/repository-snapshots.ts +259 -0
  94. package/src/service.ts +446 -11
  95. package/src/store.ts +60 -4
  96. package/src/supervisor.ts +294 -41
  97. package/src/task-budget.ts +24 -11
  98. package/src/task-context.ts +17 -0
  99. package/src/task-driver.ts +51 -15
  100. package/src/task-policy.ts +23 -3
  101. package/src/task-verification.ts +2 -0
  102. package/src/task-work.ts +46 -1
  103. package/src/team-work.ts +554 -0
  104. package/src/tui-client.ts +5 -0
  105. package/src/tui-view.ts +164 -12
  106. package/src/tui.ts +5 -2
  107. package/src/usage.ts +17 -0
  108. package/src/worker-boundary.ts +395 -0
  109. package/src/worker-workspaces.ts +390 -0
  110. package/src/workspaces.ts +4 -2
@@ -0,0 +1,206 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { TextDecoder } from 'node:util';
3
+ import { ServiceError } from './protocol.js';
4
+ export const HARNESS_RPC_LIMITS = {
5
+ frame: 1024 * 1024,
6
+ total: 4 * 1024 * 1024,
7
+ pending: 16,
8
+ timeout: 10000,
9
+ };
10
+ /** Bounded stdio framing only. A launch is not a workspace or credential sandbox. */
11
+ export class HarnessRpc {
12
+ launch;
13
+ handlers;
14
+ process;
15
+ serial = 0;
16
+ received = 0;
17
+ stderr = 0;
18
+ buffer = '';
19
+ decoder = new TextDecoder('utf-8', { fatal: true });
20
+ pending = new Map();
21
+ inbound = new Set();
22
+ failure;
23
+ stopping = false;
24
+ exited;
25
+ constructor(launch, handlers = {}) {
26
+ this.launch = launch;
27
+ this.handlers = handlers;
28
+ this.process = spawn(launch.executable, launch.args, {
29
+ cwd: launch.cwd,
30
+ env: launch.env,
31
+ stdio: ['pipe', 'pipe', 'pipe'],
32
+ });
33
+ this.exited = new Promise((resolve) => this.process.once('close', () => {
34
+ if (!this.stopping)
35
+ this.fail(new Error('Harness exited before connection closed'));
36
+ resolve();
37
+ }));
38
+ this.process.on('error', (error) => this.fail(error));
39
+ this.process.stdin.on('error', (error) => this.fail(error));
40
+ this.process.stderr.on('data', (bytes) => {
41
+ this.stderr += bytes.length;
42
+ if (this.stderr > HARNESS_RPC_LIMITS.total)
43
+ this.fail(new Error('Harness stderr exceeded its bound'));
44
+ });
45
+ this.process.stdout.on('data', (bytes) => {
46
+ try {
47
+ this.received += bytes.length;
48
+ if (this.received > HARNESS_RPC_LIMITS.total)
49
+ throw new Error('Harness output exceeded its bound');
50
+ this.buffer += this.decoder.decode(bytes, { stream: true });
51
+ let end;
52
+ while ((end = this.buffer.indexOf('\n')) >= 0) {
53
+ const line = this.buffer.slice(0, end);
54
+ this.buffer = this.buffer.slice(end + 1);
55
+ if (Buffer.byteLength(line) > HARNESS_RPC_LIMITS.frame)
56
+ throw new Error('Harness frame exceeded its bound');
57
+ if (!line.trim())
58
+ throw new Error('Empty harness frame');
59
+ this.message(JSON.parse(line));
60
+ }
61
+ if (Buffer.byteLength(this.buffer) > HARNESS_RPC_LIMITS.frame)
62
+ throw new Error('Harness frame exceeded its bound');
63
+ }
64
+ catch (error) {
65
+ this.fail(error instanceof Error ? error : new Error(String(error)));
66
+ }
67
+ });
68
+ this.process.stdout.on('end', () => {
69
+ if (!this.stopping &&
70
+ (this.buffer.length ||
71
+ (() => {
72
+ try {
73
+ return Boolean(this.decoder.decode());
74
+ }
75
+ catch {
76
+ return true;
77
+ }
78
+ })()))
79
+ this.fail(new Error('Truncated harness frame'));
80
+ });
81
+ }
82
+ object(value) {
83
+ if (!value || typeof value !== 'object' || Array.isArray(value))
84
+ throw new Error('Invalid harness object');
85
+ return value;
86
+ }
87
+ message(value) {
88
+ if (this.failure || this.stopping)
89
+ return;
90
+ const message = this.object(value);
91
+ if (this.launch.dialect === 'acp' ? message.jsonrpc !== '2.0' : message.jsonrpc !== undefined)
92
+ throw new Error('Harness RPC dialect mismatch');
93
+ if (typeof message.method === 'string') {
94
+ if (message.method.length > 192 ||
95
+ Object.hasOwn(message, 'result') ||
96
+ Object.hasOwn(message, 'error'))
97
+ throw new Error('Invalid harness request envelope');
98
+ const params = message.params === undefined ? {} : this.object(message.params);
99
+ if (message.id === undefined) {
100
+ this.handlers.notification?.(message.method, params);
101
+ return;
102
+ }
103
+ if ((typeof message.id !== 'string' && typeof message.id !== 'number') ||
104
+ (typeof message.id === 'string' && message.id.length > 256) ||
105
+ (typeof message.id === 'number' && !Number.isSafeInteger(message.id)))
106
+ throw new Error('Invalid harness request ID');
107
+ const key = JSON.stringify(message.id);
108
+ if (this.inbound.has(key) || this.inbound.size >= HARNESS_RPC_LIMITS.pending)
109
+ throw new Error('Duplicate or excessive harness requests');
110
+ this.inbound.add(key);
111
+ Promise.resolve()
112
+ .then(() => {
113
+ if (!this.handlers.request)
114
+ throw new ServiceError('harness_permission_denied', 'Harness client authority is unavailable');
115
+ return this.handlers.request(message.method, params);
116
+ })
117
+ .then((result) => this.send({ id: message.id, result: result ?? {} }), () => this.send({
118
+ id: message.id,
119
+ error: { code: -32000, message: 'Harness request denied' },
120
+ }))
121
+ .catch((error) => this.fail(error))
122
+ .finally(() => this.inbound.delete(key));
123
+ return;
124
+ }
125
+ if (!Number.isSafeInteger(message.id) ||
126
+ Object.hasOwn(message, 'result') === Object.hasOwn(message, 'error'))
127
+ throw new Error('Invalid harness reply');
128
+ const waiter = this.pending.get(message.id);
129
+ if (!waiter)
130
+ throw new Error('Unowned or duplicate harness reply');
131
+ this.pending.delete(message.id);
132
+ clearTimeout(waiter.timer);
133
+ if (Object.hasOwn(message, 'error'))
134
+ waiter.reject(new ServiceError('harness_rpc_error', 'Harness rejected the request'));
135
+ else {
136
+ try {
137
+ waiter.resolve(this.object(message.result));
138
+ }
139
+ catch (error) {
140
+ waiter.reject(error);
141
+ throw error;
142
+ }
143
+ }
144
+ }
145
+ send(value) {
146
+ if (this.failure || this.stopping)
147
+ throw this.failure ?? new Error('Harness connection closed');
148
+ const frame = JSON.stringify({ ...(this.launch.dialect === 'acp' ? { jsonrpc: '2.0' } : {}), ...value }) +
149
+ '\n';
150
+ if (Buffer.byteLength(frame) > HARNESS_RPC_LIMITS.frame)
151
+ throw new Error('Outbound harness frame exceeded its bound');
152
+ if (this.process.stdin.writableLength + Buffer.byteLength(frame) > HARNESS_RPC_LIMITS.total)
153
+ throw new Error('Harness input backpressure exceeded its bound');
154
+ this.process.stdin.write(frame);
155
+ }
156
+ request(method, params, timeout = HARNESS_RPC_LIMITS.timeout) {
157
+ if (this.failure || this.stopping)
158
+ return Promise.reject(this.failure ?? new Error('Harness connection closed'));
159
+ if (this.pending.size >= HARNESS_RPC_LIMITS.pending ||
160
+ !Number.isSafeInteger(timeout) ||
161
+ timeout < 1 ||
162
+ timeout > 300000)
163
+ return Promise.reject(new Error('Harness request bound exceeded'));
164
+ const id = ++this.serial;
165
+ return new Promise((resolve, reject) => {
166
+ const timer = setTimeout(() => this.fail(new ServiceError('harness_outcome_unknown', 'Harness reply deadline reached; the request must not be replayed')), timeout);
167
+ this.pending.set(id, { resolve, reject, timer });
168
+ try {
169
+ this.send({ id, method, params });
170
+ }
171
+ catch (error) {
172
+ this.fail(error);
173
+ }
174
+ });
175
+ }
176
+ notify(method, params) {
177
+ this.send({ method, params });
178
+ }
179
+ fail(error) {
180
+ if (this.failure || this.stopping)
181
+ return;
182
+ this.failure = error;
183
+ for (const waiter of this.pending.values()) {
184
+ clearTimeout(waiter.timer);
185
+ waiter.reject(error);
186
+ }
187
+ this.pending.clear();
188
+ this.process.kill('SIGKILL');
189
+ // A failed persistence observer must not leave the transport usable or make a timer throw.
190
+ // Pending callers already reject; the committed dispatch intent remains for recovery.
191
+ try {
192
+ this.handlers.failed?.(error);
193
+ }
194
+ catch { }
195
+ }
196
+ async close() {
197
+ this.stopping = true;
198
+ for (const waiter of this.pending.values()) {
199
+ clearTimeout(waiter.timer);
200
+ waiter.reject(new Error('Harness connection closed'));
201
+ }
202
+ this.pending.clear();
203
+ this.process.kill('SIGKILL');
204
+ await this.exited;
205
+ }
206
+ }
@@ -0,0 +1,87 @@
1
+ import { HARNESS_TARGETS } from './harness-probe.js';
2
+ import { ServiceError } from './protocol.js';
3
+ /** Correlation only: these IDs never authenticate a worker or grant operator authority. */
4
+ export function harnessToolIds(target, version, intent) {
5
+ if (!Object.hasOwn(HARNESS_TARGETS, target) ||
6
+ HARNESS_TARGETS[target].version !== version ||
7
+ !/^[a-zA-Z0-9_-]{1,128}$/.test(intent.id) ||
8
+ !/^[a-zA-Z0-9_-]{1,128}$/.test(intent.name))
9
+ throw new ServiceError('harness_tool_identity', 'Unproved runtime or malformed model tool identity');
10
+ // Gemini 0.64.0 namespaces model-call IDs when projecting them into ACP tool calls.
11
+ return target === 'gemini-acp' ? [intent.id, intent.name + '__' + intent.id] : [intent.id];
12
+ }
13
+ /** Parse only literal shell argv displays; never evaluate expansions, redirections or operators. */
14
+ function literalWords(display) {
15
+ const words = [];
16
+ let word = '', quote = '', present = false;
17
+ for (let i = 0; i < display.length; i++) {
18
+ const c = display[i];
19
+ if (quote === "'") {
20
+ if (c === "'")
21
+ quote = '';
22
+ else
23
+ word += c;
24
+ }
25
+ else if (c === '\\') {
26
+ const next = display[++i];
27
+ if (next === undefined || next === '\n')
28
+ return null;
29
+ if (quote === '"' && !['"', '\\', '$', '`'].includes(next))
30
+ word += '\\';
31
+ word += next;
32
+ present = true;
33
+ }
34
+ else if (quote === '"') {
35
+ if (c === '"')
36
+ quote = '';
37
+ else if (c === '$' || c === '`')
38
+ return null;
39
+ else
40
+ word += c;
41
+ }
42
+ else if (c === "'" || c === '"') {
43
+ quote = c;
44
+ present = true;
45
+ }
46
+ else if (c === '\n' || c === '\r')
47
+ return null;
48
+ else if (/\s/.test(c)) {
49
+ if (present)
50
+ words.push(word);
51
+ word = '';
52
+ present = false;
53
+ }
54
+ else {
55
+ if (';&|<>()$`*?[]{}~#'.includes(c))
56
+ return null;
57
+ word += c;
58
+ present = true;
59
+ }
60
+ }
61
+ if (quote)
62
+ return null;
63
+ if (present)
64
+ words.push(word);
65
+ return words;
66
+ }
67
+ /** Correlate the pinned app-server's concrete shell display with an independently captured model call. No grant is implied. */
68
+ export function matchesCodexCommandScope(version, intent, params) {
69
+ if (version !== HARNESS_TARGETS['codex-app-server'].version ||
70
+ !intent ||
71
+ intent.name !== 'exec_command' ||
72
+ intent.id !== params.itemId ||
73
+ typeof intent.arguments.cmd !== 'string' ||
74
+ typeof params.command !== 'string' ||
75
+ typeof intent.arguments.workdir !== 'string' ||
76
+ params.cwd !== intent.arguments.workdir ||
77
+ intent.arguments.shell !== undefined ||
78
+ intent.arguments.login === false)
79
+ return false;
80
+ const words = literalWords(params.command);
81
+ return (words?.length === 3 &&
82
+ // The pinned image exposes Bash at both paths. Codex selects /bin/bash
83
+ // for its UID-1000 passwd entry and /usr/bin/bash for an unmapped UID.
84
+ ['/usr/bin/bash', '/bin/bash'].includes(words[0]) &&
85
+ words[1] === '-lc' &&
86
+ words[2] === intent.arguments.cmd);
87
+ }
@@ -0,0 +1,387 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { ServiceError } from './protocol.js';
3
+ function invalid() {
4
+ throw new ServiceError('harness_wire_unsupported', 'Payload exceeds the admitted text/custom-tool wire subset');
5
+ }
6
+ const object = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : invalid();
7
+ const list = (value) => Array.isArray(value) && value.length <= 512 ? value.map(object) : invalid();
8
+ const text = (value) => (typeof value === 'string' ? value : invalid());
9
+ const id = (value) => typeof value === 'string' && /^[a-zA-Z0-9_-]{1,128}$/.test(value) ? value : invalid();
10
+ /** Explicit protocol translation to the operator-declared Claude backend, never a Google/OpenAI account claim. */
11
+ export class HarnessWireBridge {
12
+ backend;
13
+ wire;
14
+ onTool;
15
+ constructor(backend, wire, onTool) {
16
+ this.backend = backend;
17
+ this.wire = wire;
18
+ this.onTool = onTool;
19
+ if (!wire.model ||
20
+ !wire.tools.length ||
21
+ wire.tools.some((n) => !/^[a-zA-Z0-9_-]{1,128}$/.test(n)))
22
+ throw Error('Invalid frozen harness wire policy');
23
+ }
24
+ tool(name) {
25
+ const n = text(name);
26
+ if (!this.wire.tools.includes(n))
27
+ invalid();
28
+ return n;
29
+ }
30
+ append(t, role, content) {
31
+ if (!content.length)
32
+ return;
33
+ const previous = t.messages.at(-1);
34
+ if (previous?.role === role)
35
+ previous.content.push(...content);
36
+ else
37
+ t.messages.push({ role, content });
38
+ }
39
+ google(body) {
40
+ if (Object.keys(body).some((k) => !['contents', 'systemInstruction', 'tools', 'generationConfig', 'toolConfig'].includes(k)))
41
+ invalid();
42
+ const t = { system: [], messages: [], tools: [] }, pending = new Map(), byName = new Map();
43
+ if (body.systemInstruction)
44
+ for (const part of list(object(body.systemInstruction).parts))
45
+ t.system.push(text(part.text));
46
+ for (const tool of list(body.tools ?? []))
47
+ for (const declaration of list(tool.functionDeclarations ?? [])) {
48
+ const name = text(declaration.name);
49
+ if (!this.wire.tools.includes(name))
50
+ continue;
51
+ t.tools.push({
52
+ name,
53
+ description: typeof declaration.description === 'string' ? declaration.description : '',
54
+ input_schema: object(declaration.parametersJsonSchema ?? declaration.parameters),
55
+ });
56
+ }
57
+ for (const [position, message] of list(body.contents).entries()) {
58
+ if (!['user', 'model'].includes(message.role))
59
+ invalid();
60
+ const parts = [];
61
+ for (const [index, part] of list(message.parts).entries()) {
62
+ if (typeof part.text === 'string') {
63
+ parts.push({ type: 'text', text: part.text });
64
+ continue;
65
+ }
66
+ if (part.functionCall && message.role === 'model') {
67
+ const call = object(part.functionCall), name = this.tool(call.name), callId = call.id === undefined ? 'google_' + position + '_' + index : id(call.id);
68
+ if (pending.has(callId))
69
+ invalid();
70
+ pending.set(callId, name);
71
+ byName.set(name, [...(byName.get(name) ?? []), callId]);
72
+ parts.push({ type: 'tool_use', id: callId, name, input: object(call.args) });
73
+ continue;
74
+ }
75
+ if (part.functionResponse && message.role === 'user') {
76
+ const reply = object(part.functionResponse), name = this.tool(reply.name), options = byName.get(name) ?? [], callId = reply.id === undefined
77
+ ? options.length === 1
78
+ ? options[0]
79
+ : invalid()
80
+ : id(reply.id);
81
+ if (pending.get(callId) !== name)
82
+ invalid();
83
+ pending.delete(callId);
84
+ byName.set(name, options.filter((n) => n !== callId));
85
+ parts.push({
86
+ type: 'tool_result',
87
+ tool_use_id: callId,
88
+ content: JSON.stringify(reply.response),
89
+ });
90
+ continue;
91
+ }
92
+ invalid();
93
+ }
94
+ this.append(t, message.role === 'model' ? 'assistant' : 'user', parts);
95
+ }
96
+ if (pending.size)
97
+ invalid();
98
+ return t;
99
+ }
100
+ responses(body) {
101
+ if (body.model !== this.wire.model || body.previous_response_id)
102
+ invalid();
103
+ const t = { system: [], messages: [], tools: [] }, pending = new Set();
104
+ if (body.instructions !== undefined)
105
+ t.system.push(text(body.instructions));
106
+ for (const tool of list(body.tools ?? [])) {
107
+ if (tool.type !== 'function' || !this.wire.tools.includes(tool.name))
108
+ continue;
109
+ t.tools.push({
110
+ name: this.tool(tool.name),
111
+ description: typeof tool.description === 'string' ? tool.description : '',
112
+ input_schema: object(tool.parameters),
113
+ });
114
+ }
115
+ for (const item of list(body.input)) {
116
+ if (item.type === 'message' || item.role) {
117
+ if (!['user', 'assistant', 'system', 'developer'].includes(item.role))
118
+ invalid();
119
+ const parts = typeof item.content === 'string'
120
+ ? [{ text: item.content }]
121
+ : list(item.content).map((p) => {
122
+ if (!['input_text', 'output_text'].includes(p.type))
123
+ invalid();
124
+ return { text: text(p.text) };
125
+ });
126
+ if (['system', 'developer'].includes(item.role))
127
+ t.system.push(parts.map((p) => p.text).join('\n'));
128
+ else
129
+ this.append(t, item.role, parts.map((p) => ({ type: 'text', text: p.text })));
130
+ continue;
131
+ }
132
+ if (item.type === 'function_call') {
133
+ const callId = id(item.call_id);
134
+ if (pending.has(callId))
135
+ invalid();
136
+ pending.add(callId);
137
+ let input;
138
+ try {
139
+ input = object(JSON.parse(text(item.arguments)));
140
+ }
141
+ catch {
142
+ invalid();
143
+ }
144
+ this.append(t, 'assistant', [
145
+ { type: 'tool_use', id: callId, name: this.tool(item.name), input },
146
+ ]);
147
+ continue;
148
+ }
149
+ if (item.type === 'function_call_output') {
150
+ const callId = id(item.call_id);
151
+ if (!pending.delete(callId))
152
+ invalid();
153
+ const output = typeof item.output === 'string' ? item.output : JSON.stringify(item.output);
154
+ this.append(t, 'user', [{ type: 'tool_result', tool_use_id: callId, content: output }]);
155
+ continue;
156
+ }
157
+ // A foreign provider's opaque thinking signature is never forwarded as Claude authority.
158
+ if (item.type === 'reasoning')
159
+ continue;
160
+ invalid();
161
+ }
162
+ if (pending.size)
163
+ invalid();
164
+ return t;
165
+ }
166
+ async request(request) {
167
+ if (request.method !== 'POST' || request.body.length > 2 * 1024 * 1024)
168
+ invalid();
169
+ const expected = this.wire.protocol === 'google'
170
+ ? '/v1beta/models/' + this.wire.model + ':streamGenerateContent?alt=sse'
171
+ : '/v1/responses';
172
+ if (request.path !== expected)
173
+ invalid();
174
+ let raw;
175
+ try {
176
+ raw = object(JSON.parse(request.body.toString('utf8')));
177
+ }
178
+ catch {
179
+ invalid();
180
+ }
181
+ const transcript = this.wire.protocol === 'google' ? this.google(raw) : this.responses(raw);
182
+ const requested = this.wire.protocol === 'google'
183
+ ? raw.generationConfig?.maxOutputTokens
184
+ : raw.max_output_tokens;
185
+ if (requested !== undefined && (!Number.isSafeInteger(requested) || requested < 1))
186
+ invalid();
187
+ const body = {
188
+ model: this.backend.policy.model,
189
+ max_tokens: Math.min(requested ?? this.backend.policy.output, this.backend.policy.output),
190
+ stream: false,
191
+ system: transcript.system.join('\n\n'),
192
+ messages: transcript.messages,
193
+ ...(transcript.tools.length
194
+ ? {
195
+ tools: transcript.tools,
196
+ tool_choice: { type: 'auto', disable_parallel_tool_use: true },
197
+ }
198
+ : {}),
199
+ };
200
+ const result = await this.backend.request({
201
+ ...request,
202
+ path: '/v1/messages',
203
+ body: Buffer.from(JSON.stringify(body)),
204
+ });
205
+ if (result.status !== 200)
206
+ return {
207
+ status: result.status,
208
+ content_type: 'application/json',
209
+ body: Buffer.from(JSON.stringify({
210
+ error: {
211
+ code: result.status,
212
+ message: 'Declared model backend rejected this request',
213
+ status: 'FAILED_PRECONDITION',
214
+ },
215
+ })),
216
+ };
217
+ let bytes;
218
+ if (Buffer.isBuffer(result.body))
219
+ bytes = result.body;
220
+ else {
221
+ const chunks = [];
222
+ let length = 0;
223
+ for await (const chunk of result.body) {
224
+ length += chunk.length;
225
+ if (length > 8 * 1024 * 1024)
226
+ invalid();
227
+ chunks.push(Buffer.from(chunk));
228
+ }
229
+ bytes = Buffer.concat(chunks);
230
+ }
231
+ const response = object(JSON.parse(bytes.toString('utf8'))), content = list(response.content), usage = object(response.usage ?? {});
232
+ for (const part of content)
233
+ if (part.type === 'tool_use') {
234
+ this.tool(part.name);
235
+ this.onTool?.({
236
+ id: id(part.id),
237
+ name: this.tool(part.name),
238
+ arguments: structuredClone(object(part.input)),
239
+ });
240
+ }
241
+ else if (!['text', 'thinking', 'redacted_thinking'].includes(part.type))
242
+ invalid();
243
+ if (this.wire.protocol === 'google') {
244
+ const parts = content.flatMap((p) => p.type === 'text'
245
+ ? [{ text: text(p.text) }]
246
+ : p.type === 'tool_use'
247
+ ? [{ functionCall: { id: id(p.id), name: this.tool(p.name), args: object(p.input) } }]
248
+ : []);
249
+ const payload = {
250
+ candidates: [
251
+ {
252
+ index: 0,
253
+ content: { role: 'model', parts },
254
+ finishReason: response.stop_reason === 'max_tokens' ? 'MAX_TOKENS' : 'STOP',
255
+ },
256
+ ],
257
+ usageMetadata: {
258
+ promptTokenCount: usage.input_tokens ?? 0,
259
+ candidatesTokenCount: usage.output_tokens ?? 0,
260
+ totalTokenCount: (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0),
261
+ },
262
+ modelVersion: this.wire.model,
263
+ responseId: response.id,
264
+ };
265
+ return {
266
+ status: 200,
267
+ content_type: 'text/event-stream',
268
+ body: Buffer.from('data: ' + JSON.stringify(payload) + '\n\n'),
269
+ };
270
+ }
271
+ const responseId = 'resp_' + randomUUID().replaceAll('-', ''), output = content.flatMap((p) => p.type === 'text'
272
+ ? [
273
+ {
274
+ id: 'msg_' + randomUUID().replaceAll('-', ''),
275
+ type: 'message',
276
+ status: 'completed',
277
+ role: 'assistant',
278
+ content: [{ type: 'output_text', text: text(p.text), annotations: [] }],
279
+ },
280
+ ]
281
+ : p.type === 'tool_use'
282
+ ? [
283
+ {
284
+ id: 'fc_' + id(p.id),
285
+ type: 'function_call',
286
+ status: 'completed',
287
+ call_id: id(p.id),
288
+ name: this.tool(p.name),
289
+ arguments: JSON.stringify(object(p.input)),
290
+ },
291
+ ]
292
+ : []);
293
+ const complete = {
294
+ id: responseId,
295
+ object: 'response',
296
+ created_at: Math.floor(Date.now() / 1000),
297
+ status: response.stop_reason === 'max_tokens' ? 'incomplete' : 'completed',
298
+ error: null,
299
+ incomplete_details: response.stop_reason === 'max_tokens' ? { reason: 'max_output_tokens' } : null,
300
+ model: this.wire.model,
301
+ output,
302
+ usage: {
303
+ input_tokens: usage.input_tokens ?? 0,
304
+ output_tokens: usage.output_tokens ?? 0,
305
+ total_tokens: (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0),
306
+ input_tokens_details: { cached_tokens: usage.cache_read_input_tokens ?? 0 },
307
+ output_tokens_details: { reasoning_tokens: 0 },
308
+ },
309
+ parallel_tool_calls: false,
310
+ };
311
+ if (raw.stream !== true)
312
+ return {
313
+ status: 200,
314
+ content_type: 'application/json',
315
+ body: Buffer.from(JSON.stringify(complete)),
316
+ };
317
+ let serial = 0;
318
+ const events = [
319
+ { type: 'response.created', response: { ...complete, status: 'in_progress', output: [] } },
320
+ ];
321
+ for (const [index, item] of output.entries()) {
322
+ events.push({
323
+ type: 'response.output_item.added',
324
+ output_index: index,
325
+ item: {
326
+ ...item,
327
+ status: 'in_progress',
328
+ ...(item.type === 'function_call' ? { arguments: '' } : { content: [] }),
329
+ },
330
+ });
331
+ if (item.type === 'function_call')
332
+ events.push({
333
+ type: 'response.function_call_arguments.delta',
334
+ item_id: item.id,
335
+ output_index: index,
336
+ delta: item.arguments,
337
+ }, {
338
+ type: 'response.function_call_arguments.done',
339
+ item_id: item.id,
340
+ output_index: index,
341
+ arguments: item.arguments,
342
+ });
343
+ else {
344
+ events.push({
345
+ type: 'response.content_part.added',
346
+ item_id: item.id,
347
+ output_index: index,
348
+ content_index: 0,
349
+ part: { type: 'output_text', text: '', annotations: [] },
350
+ }, {
351
+ type: 'response.output_text.delta',
352
+ item_id: item.id,
353
+ output_index: index,
354
+ content_index: 0,
355
+ delta: item.content[0].text,
356
+ }, {
357
+ type: 'response.output_text.done',
358
+ item_id: item.id,
359
+ output_index: index,
360
+ content_index: 0,
361
+ text: item.content[0].text,
362
+ }, {
363
+ type: 'response.content_part.done',
364
+ item_id: item.id,
365
+ output_index: index,
366
+ content_index: 0,
367
+ part: item.content[0],
368
+ });
369
+ }
370
+ events.push({ type: 'response.output_item.done', output_index: index, item });
371
+ }
372
+ events.push({
373
+ type: complete.status === 'completed' ? 'response.completed' : 'response.incomplete',
374
+ response: complete,
375
+ });
376
+ return {
377
+ status: 200,
378
+ content_type: 'text/event-stream',
379
+ body: Buffer.from(events
380
+ .map((event) => {
381
+ const value = { ...event, sequence_number: serial++ };
382
+ return 'event: ' + event.type + '\ndata: ' + JSON.stringify(value) + '\n\n';
383
+ })
384
+ .join('')),
385
+ };
386
+ }
387
+ }