@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.
- package/CHANGELOG.md +15 -0
- package/README.md +25 -17
- package/dist/src/a2a-backend.js +225 -0
- package/dist/src/a2a-inbound.js +487 -0
- package/dist/src/a2a-peer.js +370 -0
- package/dist/src/a2a-protocol.js +250 -0
- package/dist/src/agent-profiles.js +289 -0
- package/dist/src/children.js +132 -26
- package/dist/src/claude-provider.js +2 -179
- package/dist/src/cli-help.js +107 -6
- package/dist/src/cli.js +477 -13
- package/dist/src/config.js +26 -3
- package/dist/src/credentials.js +7 -2
- package/dist/src/external-runtime.js +1 -0
- package/dist/src/external-work.js +583 -0
- package/dist/src/harness-backend.js +466 -0
- package/dist/src/harness-container.js +450 -0
- package/dist/src/harness-model-gateway.js +104 -0
- package/dist/src/harness-prepare.js +88 -0
- package/dist/src/harness-probe.js +111 -0
- package/dist/src/harness-protocol.js +325 -0
- package/dist/src/harness-readiness.js +50 -0
- package/dist/src/harness-rpc.js +206 -0
- package/dist/src/harness-tool-identity.js +87 -0
- package/dist/src/harness-wire-bridge.js +387 -0
- package/dist/src/host.js +6 -1
- package/dist/src/house-extension.js +147 -64
- package/dist/src/live-provider.js +301 -0
- package/dist/src/managed.js +33 -3
- package/dist/src/protocol.js +9 -5
- package/dist/src/provider-auth.js +300 -0
- package/dist/src/provider-config.js +65 -17
- package/dist/src/provider-model.js +78 -0
- package/dist/src/provider-payload.js +264 -0
- package/dist/src/provider-transport.js +36 -0
- package/dist/src/provider-usage.js +59 -0
- package/dist/src/repository-snapshots.js +219 -0
- package/dist/src/service.js +274 -9
- package/dist/src/store.js +44 -2
- package/dist/src/supervisor.js +246 -35
- package/dist/src/task-budget.js +17 -8
- package/dist/src/task-context.js +15 -0
- package/dist/src/task-driver.js +38 -14
- package/dist/src/task-policy.js +20 -1
- package/dist/src/task-verification.js +3 -0
- package/dist/src/task-work.js +31 -0
- package/dist/src/team-work.js +378 -0
- package/dist/src/tui-view.js +65 -10
- package/dist/src/tui.js +4 -2
- package/dist/src/usage.js +18 -0
- package/dist/src/worker-boundary.js +298 -0
- package/dist/src/worker-workspaces.js +274 -0
- package/dist/src/workspaces.js +6 -2
- package/harness-runtime/codex-pins.json +10 -0
- package/harness-runtime/package-lock.json +560 -0
- package/harness-runtime/package.json +9 -0
- package/npm-shrinkwrap.json +57 -2
- package/package.json +2 -1
- package/src/a2a-backend.ts +254 -0
- package/src/a2a-inbound.ts +585 -0
- package/src/a2a-peer.ts +441 -0
- package/src/a2a-protocol.ts +286 -0
- package/src/agent-profiles.ts +379 -0
- package/src/children.ts +168 -27
- package/src/claude-provider.ts +2 -189
- package/src/cli-help.ts +107 -6
- package/src/cli.ts +602 -12
- package/src/config.ts +34 -3
- package/src/credentials.ts +9 -2
- package/src/external-runtime.ts +54 -0
- package/src/external-work.ts +663 -0
- package/src/harness-backend.ts +557 -0
- package/src/harness-container.ts +543 -0
- package/src/harness-model-gateway.ts +138 -0
- package/src/harness-prepare.ts +113 -0
- package/src/harness-probe.ts +150 -0
- package/src/harness-protocol.ts +388 -0
- package/src/harness-readiness.ts +71 -0
- package/src/harness-rpc.ts +244 -0
- package/src/harness-tool-identity.ts +91 -0
- package/src/harness-wire-bridge.ts +418 -0
- package/src/host.ts +11 -6
- package/src/house-extension.ts +165 -66
- package/src/live-provider.ts +347 -0
- package/src/managed.ts +43 -4
- package/src/protocol.ts +36 -8
- package/src/provider-auth.ts +336 -0
- package/src/provider-config.ts +89 -18
- package/src/provider-model.ts +86 -0
- package/src/provider-payload.ts +263 -0
- package/src/provider-transport.ts +51 -0
- package/src/provider-usage.ts +64 -0
- package/src/repository-snapshots.ts +259 -0
- package/src/service.ts +446 -11
- package/src/store.ts +60 -4
- package/src/supervisor.ts +294 -41
- package/src/task-budget.ts +24 -11
- package/src/task-context.ts +17 -0
- package/src/task-driver.ts +51 -15
- package/src/task-policy.ts +23 -3
- package/src/task-verification.ts +2 -0
- package/src/task-work.ts +46 -1
- package/src/team-work.ts +554 -0
- package/src/tui-client.ts +5 -0
- package/src/tui-view.ts +164 -12
- package/src/tui.ts +5 -2
- package/src/usage.ts +17 -0
- package/src/worker-boundary.ts +395 -0
- package/src/worker-workspaces.ts +390 -0
- package/src/workspaces.ts +4 -2
package/dist/src/host.js
CHANGED
|
@@ -4,6 +4,8 @@ import { readFileSync, readdirSync, mkdirSync, openSync, writeFileSync, fsyncSyn
|
|
|
4
4
|
import { join, resolve, isAbsolute, sep } from 'node:path';
|
|
5
5
|
import { ROOT, bundleDigest } from './config.js';
|
|
6
6
|
import { digest, object, string, ServiceError } from './protocol.js';
|
|
7
|
+
import { bindProviderConfiguration } from './provider-model.js';
|
|
8
|
+
import { resolveProviderAuth } from './provider-auth.js';
|
|
7
9
|
import { providerRoute } from './provider-config.js';
|
|
8
10
|
import { engineRequest } from './execution-boundary.js';
|
|
9
11
|
import { DockerCodingExecutor } from './coding-executor.js';
|
|
@@ -41,6 +43,9 @@ export async function planHost(input) {
|
|
|
41
43
|
mkdirSync(coding_root, { recursive: true, mode: 0o700 });
|
|
42
44
|
const executor = new DockerCodingExecutor(engineRequest(coding_socket));
|
|
43
45
|
await executor.prepare(coding_root);
|
|
46
|
+
let route = providerRoute(c.provider === undefined ? 'offline' : String(c.provider), c.model, c.max_output_tokens, c.workspace_id);
|
|
47
|
+
if (route.provider !== 'salaam-proof')
|
|
48
|
+
route = bindProviderConfiguration(route, await resolveProviderAuth(route));
|
|
44
49
|
return {
|
|
45
50
|
schema: 1,
|
|
46
51
|
node: realpathSync(process.execPath),
|
|
@@ -50,7 +55,7 @@ export async function planHost(input) {
|
|
|
50
55
|
state,
|
|
51
56
|
coding_root: realpathSync(coding_root),
|
|
52
57
|
coding_socket,
|
|
53
|
-
provider:
|
|
58
|
+
provider: route,
|
|
54
59
|
profile_digest: executor.profileDigest,
|
|
55
60
|
engine_id: executor.engineId,
|
|
56
61
|
};
|
|
@@ -11,7 +11,7 @@ export default function house(pi) {
|
|
|
11
11
|
throw new Error('Missing Salaam worker configuration');
|
|
12
12
|
const c = config;
|
|
13
13
|
const route = c.route ?? { ...OFFLINE_ROUTE };
|
|
14
|
-
const bridge = (method, data = {}) => call(c.socket, `/worker/${c.session}/${method}`, { epoch: c.epoch, ...data }, c.token, 2000);
|
|
14
|
+
const bridge = (method, data = {}) => call(c.socket, `/worker/${c.session}/${method}`, { epoch: c.epoch, ...data }, c.token, method === 'provider.auth' ? 120000 : 2000);
|
|
15
15
|
let timer;
|
|
16
16
|
let shutdown;
|
|
17
17
|
const fail = async () => {
|
|
@@ -23,28 +23,29 @@ export default function house(pi) {
|
|
|
23
23
|
}
|
|
24
24
|
shutdown?.();
|
|
25
25
|
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
if (workerTools(!!c.effects, !!c.child, c.coding, c.worker_binding?.binding.tools).includes('salaam_probe'))
|
|
27
|
+
pi.registerTool({
|
|
28
|
+
name: 'salaam_probe',
|
|
29
|
+
label: 'Salaam readiness probe',
|
|
30
|
+
description: 'Verify the managed tool gate without external effects.',
|
|
31
|
+
parameters: Type.Object({ nested: Type.Optional(Type.Boolean()) }),
|
|
32
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
33
|
+
async execute(toolCallId, params, signal, _update, ctx) {
|
|
34
|
+
if (params.nested) {
|
|
35
|
+
const outcome = await ctx.executeTool('salaam_probe', {}, { signal });
|
|
36
|
+
if (outcome.isError) {
|
|
37
|
+
await fail();
|
|
38
|
+
throw new Error('Nested managed probe failed');
|
|
39
|
+
}
|
|
40
|
+
return { ...outcome.result, isError: outcome.isError };
|
|
38
41
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
},
|
|
47
|
-
});
|
|
42
|
+
await bridge('tool', { tool: 'salaam_probe', tool_call_id: toolCallId });
|
|
43
|
+
return {
|
|
44
|
+
content: [{ type: 'text', text: 'managed probe granted' }],
|
|
45
|
+
details: { granted: true },
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
});
|
|
48
49
|
const managedTool = (name, kind) => pi.registerTool({
|
|
49
50
|
name,
|
|
50
51
|
label: kind === 'review' ? 'Approved read-only review' : 'Approved fixture effect',
|
|
@@ -124,6 +125,7 @@ export default function house(pi) {
|
|
|
124
125
|
prompt: Type.Optional(Type.String({ maxLength: 32768 })),
|
|
125
126
|
budget: Type.Optional(Type.Integer({ minimum: 1, maximum: 8 })),
|
|
126
127
|
authority: Type.Optional(Type.Array(Type.String())),
|
|
128
|
+
profile: Type.Optional(Type.String({ maxLength: 128 })),
|
|
127
129
|
tasks: Type.Optional(Type.Array(Type.Object({ step_key: Type.String(), prompt: Type.String() }), {
|
|
128
130
|
maxItems: 2,
|
|
129
131
|
})),
|
|
@@ -136,7 +138,8 @@ export default function house(pi) {
|
|
|
136
138
|
let children = await Promise.all(tasks.map((task) => bridge('child.spawn', {
|
|
137
139
|
...task,
|
|
138
140
|
budget: params.budget ?? 2,
|
|
139
|
-
authority: params.authority ?? ['salaam_probe'],
|
|
141
|
+
authority: params.authority ?? (params.profile ? [] : ['salaam_probe']),
|
|
142
|
+
...(params.profile ? { profile: params.profile } : {}),
|
|
140
143
|
})));
|
|
141
144
|
while (params.wait && children.some((x) => x.state === 'accepted')) {
|
|
142
145
|
if (signal?.aborted)
|
|
@@ -180,7 +183,85 @@ export default function house(pi) {
|
|
|
180
183
|
}
|
|
181
184
|
if (c.effects)
|
|
182
185
|
managedTool('salaam_effect', 'effect');
|
|
183
|
-
if (c.
|
|
186
|
+
if (c.worker_binding?.binding.tools.includes('salaam_repository')) {
|
|
187
|
+
pi.registerTool({
|
|
188
|
+
name: 'salaam_repository',
|
|
189
|
+
label: 'Repository snapshot',
|
|
190
|
+
description: 'Inspect, list, search or read the exact admitted immutable repository snapshot. Reviewers may also inspect its diff. Paths and line references refer to this snapshot; no host file access, edits or commands are available.',
|
|
191
|
+
parameters: Type.Object({
|
|
192
|
+
action: Type.Union(['inspect', 'list', 'search', 'read', 'diff'].map((x) => Type.Literal(x))),
|
|
193
|
+
path: Type.Optional(Type.String({ maxLength: 512 })),
|
|
194
|
+
query: Type.Optional(Type.String({ maxLength: 256 })),
|
|
195
|
+
offset: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
196
|
+
}),
|
|
197
|
+
async execute(id, args) {
|
|
198
|
+
try {
|
|
199
|
+
const result = await bridge('specialist.read', { tool_call_id: id, args });
|
|
200
|
+
return {
|
|
201
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
202
|
+
details: result,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
if (error instanceof ServiceError &&
|
|
207
|
+
['not_found', 'binary_file', 'line_limit', 'unsafe_path', 'invalid_field'].includes(error.code))
|
|
208
|
+
return {
|
|
209
|
+
content: [{ type: 'text', text: error.message }],
|
|
210
|
+
details: { error: error.code },
|
|
211
|
+
isError: true,
|
|
212
|
+
};
|
|
213
|
+
await fail();
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
if (c.worker_binding?.binding.tools.includes('salaam_team')) {
|
|
220
|
+
pi.registerTool({
|
|
221
|
+
name: 'salaam_team',
|
|
222
|
+
label: 'Team inbox',
|
|
223
|
+
description: 'Send, receive or acknowledge durable messages within this task. Messages never start or continue workers; delivery and acknowledgement are distinct.',
|
|
224
|
+
parameters: Type.Object({
|
|
225
|
+
action: Type.Union(['send', 'receive', 'ack'].map((a) => Type.Literal(a))),
|
|
226
|
+
request_id: Type.String({ maxLength: 128 }),
|
|
227
|
+
recipient: Type.Optional(Type.String()),
|
|
228
|
+
text: Type.Optional(Type.String({ maxLength: 4096 })),
|
|
229
|
+
message_id: Type.Optional(Type.String()),
|
|
230
|
+
}),
|
|
231
|
+
async execute(_id, args) {
|
|
232
|
+
const { action, ...body } = args;
|
|
233
|
+
const result = await bridge('team.' + action, body);
|
|
234
|
+
return {
|
|
235
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
236
|
+
details: result,
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
if (c.worker_binding?.binding.tools.includes('salaam_findings')) {
|
|
242
|
+
pi.registerTool({
|
|
243
|
+
name: 'salaam_findings',
|
|
244
|
+
label: 'Record review findings',
|
|
245
|
+
description: 'Publish final structured analysis over the admitted snapshot. Locations and source evidence are checked. Analysis cannot complete or verify a task.',
|
|
246
|
+
parameters: Type.Object({
|
|
247
|
+
findings: Type.Array(Type.Object({
|
|
248
|
+
path: Type.String({ maxLength: 512 }),
|
|
249
|
+
line: Type.Integer({ minimum: 1 }),
|
|
250
|
+
end_line: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
251
|
+
severity: Type.Union(['critical', 'high', 'medium', 'low'].map((x) => Type.Literal(x))),
|
|
252
|
+
message: Type.String({ maxLength: 2048 }),
|
|
253
|
+
}), { maxItems: 32 }),
|
|
254
|
+
}),
|
|
255
|
+
async execute(_id, args) {
|
|
256
|
+
const result = await bridge('specialist.report', args);
|
|
257
|
+
return {
|
|
258
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
259
|
+
details: result,
|
|
260
|
+
};
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
if (c.coding) {
|
|
184
265
|
const normal = (error) => error instanceof ServiceError &&
|
|
185
266
|
[
|
|
186
267
|
'unsafe_path',
|
|
@@ -197,44 +278,45 @@ export default function house(pi) {
|
|
|
197
278
|
'command_limit',
|
|
198
279
|
'invalid_cursor',
|
|
199
280
|
].includes(error.code);
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
catch (error) {
|
|
226
|
-
if (!normal(error)) {
|
|
227
|
-
await fail();
|
|
228
|
-
throw error;
|
|
281
|
+
if (c.coding.files)
|
|
282
|
+
pi.registerTool({
|
|
283
|
+
name: 'salaam_files',
|
|
284
|
+
label: 'Guarded workspace files',
|
|
285
|
+
description: 'Inspect/list/search/read an owned workspace or patch one UTF-8 file. Read returns one-based line references and a file hash. Before patching, inspect/read current revision and hash. expected_hash=null creates a new file, content=null deletes one. The source checkout and Git metadata stay outside this workspace.',
|
|
286
|
+
parameters: Type.Object({
|
|
287
|
+
action: Type.Union(['inspect', 'list', 'search', 'read', 'patch'].map((value) => Type.Literal(value))),
|
|
288
|
+
path: Type.Optional(Type.String({ maxLength: 512 })),
|
|
289
|
+
query: Type.Optional(Type.String({ maxLength: 256 })),
|
|
290
|
+
offset: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
291
|
+
expected_revision: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
292
|
+
expected_hash: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
293
|
+
content: Type.Optional(Type.Union([Type.String({ maxLength: 262144 }), Type.Null()])),
|
|
294
|
+
}),
|
|
295
|
+
async execute(id, params) {
|
|
296
|
+
try {
|
|
297
|
+
const result = await bridge('coding.files', {
|
|
298
|
+
tool_call_id: id,
|
|
299
|
+
args: params,
|
|
300
|
+
});
|
|
301
|
+
return {
|
|
302
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
303
|
+
details: result,
|
|
304
|
+
isError: params.action === 'patch' && result.state !== 'applied',
|
|
305
|
+
};
|
|
229
306
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
307
|
+
catch (error) {
|
|
308
|
+
if (!normal(error)) {
|
|
309
|
+
await fail();
|
|
310
|
+
throw error;
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
content: [{ type: 'text', text: error.message }],
|
|
314
|
+
details: { error: error.code },
|
|
315
|
+
isError: true,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
});
|
|
238
320
|
if (c.coding.commands)
|
|
239
321
|
pi.registerTool({
|
|
240
322
|
name: 'salaam_command',
|
|
@@ -375,8 +457,8 @@ export default function house(pi) {
|
|
|
375
457
|
],
|
|
376
458
|
};
|
|
377
459
|
});
|
|
378
|
-
if (route.provider
|
|
379
|
-
registerClaude(pi, route, () => c.frame, workerTools(!!c.effects, !!c.child, c.coding), bridge, fail);
|
|
460
|
+
if (route.provider !== 'salaam-proof')
|
|
461
|
+
registerClaude(pi, route, () => c.frame, workerTools(!!c.effects, !!c.child, c.coding, c.worker_binding?.binding.tools), bridge, fail);
|
|
380
462
|
else
|
|
381
463
|
pi.registerProvider('salaam-proof', {
|
|
382
464
|
baseUrl: 'http://unused.invalid',
|
|
@@ -579,7 +661,7 @@ export default function house(pi) {
|
|
|
579
661
|
session_id: c.session,
|
|
580
662
|
epoch: c.epoch,
|
|
581
663
|
pi_session_id: ctx.sessionManager.getSessionId(),
|
|
582
|
-
capabilities: workerCapabilities(c.coding),
|
|
664
|
+
capabilities: workerCapabilities(c.coding, c.worker_binding?.binding.tools),
|
|
583
665
|
...(c.coding ? { coding: c.coding } : {}),
|
|
584
666
|
tools: pi
|
|
585
667
|
.getAllTools()
|
|
@@ -590,6 +672,7 @@ export default function house(pi) {
|
|
|
590
672
|
provider: route.provider,
|
|
591
673
|
route,
|
|
592
674
|
context_digest: textDigest(c.frame),
|
|
675
|
+
...(c.worker_binding ? { worker_binding: c.worker_binding } : {}),
|
|
593
676
|
});
|
|
594
677
|
timer = setInterval(() => {
|
|
595
678
|
void bridge('heartbeat').catch(() => {
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { createAssistantMessageEventStream, } from '@earendil-works/pi-ai';
|
|
2
|
+
import { zstdDecompressSync } from 'node:zlib';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { digest, object, textDigest, canonical } from './protocol.js';
|
|
5
|
+
import { getProvider, getProviderModel, providerHasOutputCap, } from './provider-config.js';
|
|
6
|
+
import { ProviderUsageReceipt } from './provider-usage.js';
|
|
7
|
+
import { materialize, generationOptions } from './provider-model.js';
|
|
8
|
+
import { validateProviderPayload } from './provider-payload.js';
|
|
9
|
+
import { installProviderTransport, withProviderTransport } from './provider-transport.js';
|
|
10
|
+
function freezeModel(value) {
|
|
11
|
+
const copy = structuredClone(value);
|
|
12
|
+
const freeze = (item) => {
|
|
13
|
+
if (item && typeof item === 'object' && !Object.isFrozen(item)) {
|
|
14
|
+
Object.values(item).forEach(freeze);
|
|
15
|
+
Object.freeze(item);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
freeze(copy);
|
|
19
|
+
return copy;
|
|
20
|
+
}
|
|
21
|
+
function jsonBody(value, encoding) {
|
|
22
|
+
if (encoding) {
|
|
23
|
+
if (encoding !== 'zstd' || !(value instanceof Uint8Array))
|
|
24
|
+
throw new Error('Unsupported provider body encoding');
|
|
25
|
+
value = zstdDecompressSync(value, { maxOutputLength: 1048576 });
|
|
26
|
+
}
|
|
27
|
+
if (typeof value === 'string')
|
|
28
|
+
return JSON.parse(value);
|
|
29
|
+
if (value instanceof Uint8Array)
|
|
30
|
+
return JSON.parse(Buffer.from(value).toString('utf8'));
|
|
31
|
+
throw new Error('Provider JSON body missing');
|
|
32
|
+
}
|
|
33
|
+
export function registerLiveProvider(pi, route, frame, tools, bridge, fail) {
|
|
34
|
+
const catalog = getProviderModel(route), provider = getProvider(route.provider);
|
|
35
|
+
if (!catalog || !provider)
|
|
36
|
+
throw new Error('Pinned provider model missing');
|
|
37
|
+
const rawFetch = installProviderTransport() ?? globalThis.fetch;
|
|
38
|
+
pi.registerProvider(route.provider, {
|
|
39
|
+
baseUrl: route.connection?.endpoint || catalog.baseUrl || 'https://salaam-configured.invalid',
|
|
40
|
+
apiKey: 'salaam-resolves-auth',
|
|
41
|
+
api: catalog.api,
|
|
42
|
+
models: [
|
|
43
|
+
{
|
|
44
|
+
...catalog,
|
|
45
|
+
baseUrl: route.connection?.endpoint || catalog.baseUrl || 'https://salaam-configured.invalid',
|
|
46
|
+
maxTokens: route.max_tokens,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
streamSimple(model, context, options) {
|
|
50
|
+
const output = createAssistantMessageEventStream(), source = 'salaam-request:' + randomUUID(), receipt = new ProviderUsageReceipt();
|
|
51
|
+
let failed = false, dispatched = false, status, last;
|
|
52
|
+
const normalize = (message) => ({
|
|
53
|
+
...message,
|
|
54
|
+
responseId: source,
|
|
55
|
+
});
|
|
56
|
+
void (async () => {
|
|
57
|
+
try {
|
|
58
|
+
if (model.provider !== route.provider || model.id !== route.model)
|
|
59
|
+
throw new Error('Unapproved provider route');
|
|
60
|
+
const auth = (await bridge('provider.auth', {}));
|
|
61
|
+
if (route.provider === 'amazon-bedrock') {
|
|
62
|
+
for (const name of Object.keys(process.env))
|
|
63
|
+
if (name.startsWith('AWS_'))
|
|
64
|
+
delete process.env[name];
|
|
65
|
+
Object.assign(process.env, Object.fromEntries(Object.entries(auth.env ?? {}).filter(([key]) => key.startsWith('AWS_'))));
|
|
66
|
+
}
|
|
67
|
+
const native = freezeModel(materialize({ ...catalog, maxTokens: route.max_tokens }, auth));
|
|
68
|
+
let approved, approvedDigest;
|
|
69
|
+
const validate = (payload) => validateProviderPayload(payload, route, frame(), tools, !providerHasOutputCap(route, auth));
|
|
70
|
+
const admit = async (payload) => {
|
|
71
|
+
if (dispatched)
|
|
72
|
+
throw new Error('Provider request replay denied');
|
|
73
|
+
validate(payload);
|
|
74
|
+
if (!approvedDigest)
|
|
75
|
+
throw new Error('Provider payload not approved');
|
|
76
|
+
dispatched = true;
|
|
77
|
+
await bridge('model', {
|
|
78
|
+
source_key: source,
|
|
79
|
+
provider: route.provider,
|
|
80
|
+
model: route.model,
|
|
81
|
+
context_digest: textDigest(frame()),
|
|
82
|
+
payload_digest: digest(payload),
|
|
83
|
+
payload,
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
const endpoint = (url) => {
|
|
87
|
+
const base = new URL(native.baseUrl);
|
|
88
|
+
if (url.origin !== base.origin || url.username || url.password || url.hash)
|
|
89
|
+
throw new Error('Unapproved provider endpoint');
|
|
90
|
+
const prefix = base.pathname.replace(/\/$/, '');
|
|
91
|
+
if (native.api === 'google-generative-ai' || native.api === 'google-vertex') {
|
|
92
|
+
if (!url.pathname.startsWith(prefix + '/') ||
|
|
93
|
+
!url.pathname.endsWith('/' + route.model + ':streamGenerateContent') ||
|
|
94
|
+
url.searchParams.get('alt') !== 'sse')
|
|
95
|
+
throw new Error('Unapproved Google endpoint');
|
|
96
|
+
}
|
|
97
|
+
else if (native.api === 'bedrock-converse-stream') {
|
|
98
|
+
if (decodeURIComponent(url.pathname) !== '/model/' + route.model + '/converse-stream')
|
|
99
|
+
throw new Error('Unapproved Bedrock endpoint');
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
const suffixes = native.api === 'anthropic-messages'
|
|
103
|
+
? ['/v1/messages', '/messages']
|
|
104
|
+
: native.api === 'mistral-conversations'
|
|
105
|
+
? ['/v1/chat/completions']
|
|
106
|
+
: native.api === 'openai-completions'
|
|
107
|
+
? ['/chat/completions']
|
|
108
|
+
: native.api === 'pi-messages'
|
|
109
|
+
? ['/messages']
|
|
110
|
+
: native.api === 'openai-codex-responses'
|
|
111
|
+
? ['/codex/responses']
|
|
112
|
+
: ['/responses'];
|
|
113
|
+
if (!suffixes.some((suffix) => url.pathname === prefix + suffix))
|
|
114
|
+
throw new Error('Unapproved provider path');
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const wirePayload = (payload) => {
|
|
118
|
+
if (native.api === 'google-generative-ai' || native.api === 'google-vertex') {
|
|
119
|
+
const p = object(payload);
|
|
120
|
+
if (p.cachedContent !== undefined)
|
|
121
|
+
throw new Error('Cached Google context disabled');
|
|
122
|
+
return {
|
|
123
|
+
model: route.model,
|
|
124
|
+
contents: p.contents,
|
|
125
|
+
config: { ...object(p.generationConfig), tools: p.tools, toolConfig: p.toolConfig },
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (native.api === 'bedrock-converse-stream')
|
|
129
|
+
return { ...object(payload), modelId: route.model };
|
|
130
|
+
return payload;
|
|
131
|
+
};
|
|
132
|
+
const transport = {
|
|
133
|
+
fetch: async (input, init) => {
|
|
134
|
+
try {
|
|
135
|
+
const url = new URL(input instanceof Request ? input.url : String(input));
|
|
136
|
+
endpoint(url);
|
|
137
|
+
const request = input instanceof Request ? input : undefined;
|
|
138
|
+
if ((init?.method ?? request?.method) !== 'POST')
|
|
139
|
+
throw new Error('Provider POST required');
|
|
140
|
+
const raw = init?.body ?? (request ? await request.clone().text() : undefined);
|
|
141
|
+
const payload = jsonBody(raw, new Headers(init?.headers ?? request?.headers).get('content-encoding'));
|
|
142
|
+
validate(wirePayload(payload));
|
|
143
|
+
const expectedWire = native.api === 'anthropic-messages'
|
|
144
|
+
? Object.fromEntries(Object.entries(object(approved)).filter(([key]) => key !== 'betas'))
|
|
145
|
+
: approved;
|
|
146
|
+
if (!['google-generative-ai', 'google-vertex', 'mistral-conversations'].includes(native.api) &&
|
|
147
|
+
digest(payload) !== digest(expectedWire))
|
|
148
|
+
throw new Error('Provider payload changed after validation');
|
|
149
|
+
// The SDK translates Google/Mistral parameters. Validate the final wire controls too.
|
|
150
|
+
await admit(approved);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
failed = true;
|
|
154
|
+
throw new Error('Provider request denied by Salaam');
|
|
155
|
+
}
|
|
156
|
+
return rawFetch(input, { ...init, redirect: 'error' });
|
|
157
|
+
},
|
|
158
|
+
bedrock: async (request) => {
|
|
159
|
+
try {
|
|
160
|
+
endpoint(new URL(request.protocol + '//' + request.hostname + request.path));
|
|
161
|
+
if (request.method !== 'POST')
|
|
162
|
+
throw new Error('Provider POST required');
|
|
163
|
+
const payload = wirePayload(jsonBody(request.body));
|
|
164
|
+
validate(payload);
|
|
165
|
+
// AWS serializes undefined fields away. Compare the canonical JSON representation.
|
|
166
|
+
if (canonical(payload) !== canonical(JSON.parse(JSON.stringify(approved))))
|
|
167
|
+
throw new Error('Bedrock payload changed after validation');
|
|
168
|
+
await admit(approved);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
failed = true;
|
|
172
|
+
throw new Error('Provider request denied by Salaam');
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
const headers = { ...options?.headers };
|
|
177
|
+
for (const name of Object.keys(headers))
|
|
178
|
+
if ([
|
|
179
|
+
'authorization',
|
|
180
|
+
'x-api-key',
|
|
181
|
+
'api-key',
|
|
182
|
+
'x-goog-api-key',
|
|
183
|
+
'cf-aig-authorization',
|
|
184
|
+
'anthropic-workspace-id',
|
|
185
|
+
].includes(name.toLowerCase()))
|
|
186
|
+
delete headers[name];
|
|
187
|
+
Object.assign(headers, auth.auth.headers);
|
|
188
|
+
if (route.workspace_id)
|
|
189
|
+
headers['anthropic-workspace-id'] = route.workspace_id;
|
|
190
|
+
const upstream = withProviderTransport(transport, () => provider.streamSimple(native, context, {
|
|
191
|
+
...options,
|
|
192
|
+
headers,
|
|
193
|
+
apiKey: auth.auth.apiKey,
|
|
194
|
+
env: auth.env ?? {},
|
|
195
|
+
...generationOptions(native, context, route.max_tokens),
|
|
196
|
+
transport: 'sse',
|
|
197
|
+
maxRetries: 0,
|
|
198
|
+
timeoutMs: 120000,
|
|
199
|
+
fetch: ['google-generative-ai', 'google-vertex', 'bedrock-converse-stream'].includes(native.api)
|
|
200
|
+
? undefined
|
|
201
|
+
: transport.fetch,
|
|
202
|
+
onPayload: async (payload, rawModel) => {
|
|
203
|
+
try {
|
|
204
|
+
const replacement = await options?.onPayload?.(payload, rawModel);
|
|
205
|
+
const actual = replacement === undefined ? payload : replacement;
|
|
206
|
+
validate(actual);
|
|
207
|
+
if (native.api === 'google-generative-ai' || native.api === 'google-vertex')
|
|
208
|
+
object(actual).config = {
|
|
209
|
+
...object(object(actual).config),
|
|
210
|
+
httpOptions: { retryOptions: { attempts: 1 }, timeout: 120000 },
|
|
211
|
+
};
|
|
212
|
+
approved = JSON.parse(JSON.stringify(actual));
|
|
213
|
+
approvedDigest = digest(approved);
|
|
214
|
+
return actual;
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
failed = true;
|
|
218
|
+
throw new Error('Provider payload denied');
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
onProviderStreamEvent: async (event, rawModel) => {
|
|
222
|
+
receipt.observe(catalog.api, event);
|
|
223
|
+
await options?.onProviderStreamEvent?.(event, rawModel);
|
|
224
|
+
},
|
|
225
|
+
onResponse: async (response, rawModel) => {
|
|
226
|
+
status = response.status;
|
|
227
|
+
await options?.onResponse?.(response, rawModel);
|
|
228
|
+
},
|
|
229
|
+
}));
|
|
230
|
+
for await (const event of upstream) {
|
|
231
|
+
if ('partial' in event)
|
|
232
|
+
last = normalize(event.partial);
|
|
233
|
+
if (event.type === 'done') {
|
|
234
|
+
if (!dispatched)
|
|
235
|
+
throw new Error('Provider returned without admission');
|
|
236
|
+
const message = normalize(event.message);
|
|
237
|
+
last = message;
|
|
238
|
+
if (receipt.complete)
|
|
239
|
+
await bridge('usage', {
|
|
240
|
+
source_key: source,
|
|
241
|
+
usage: message.usage,
|
|
242
|
+
provider_response_id: event.message.responseId ?? null,
|
|
243
|
+
});
|
|
244
|
+
output.push({ ...event, message });
|
|
245
|
+
}
|
|
246
|
+
else if (event.type === 'error') {
|
|
247
|
+
const message = normalize(event.error);
|
|
248
|
+
const code = /^(?:Error:\s*)?(\d{3})\b/.exec(message.errorMessage ?? '')?.[1];
|
|
249
|
+
status ??= code ? Number(code) : undefined;
|
|
250
|
+
delete message.diagnostics;
|
|
251
|
+
message.content = [];
|
|
252
|
+
message.errorMessage = options?.signal?.aborted
|
|
253
|
+
? 'Provider request cancelled'
|
|
254
|
+
: `Provider request failed${status ? ` (HTTP ${status})` : ''}`;
|
|
255
|
+
output.push({ ...event, error: message });
|
|
256
|
+
}
|
|
257
|
+
else
|
|
258
|
+
output.push(('partial' in event
|
|
259
|
+
? { ...event, partial: last }
|
|
260
|
+
: event));
|
|
261
|
+
}
|
|
262
|
+
output.end();
|
|
263
|
+
if (failed)
|
|
264
|
+
await fail();
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
const error = {
|
|
268
|
+
role: 'assistant',
|
|
269
|
+
content: [],
|
|
270
|
+
api: catalog.api,
|
|
271
|
+
provider: route.provider,
|
|
272
|
+
model: route.model,
|
|
273
|
+
timestamp: Date.now(),
|
|
274
|
+
responseId: source,
|
|
275
|
+
stopReason: options?.signal?.aborted ? 'aborted' : 'error',
|
|
276
|
+
usage: last?.usage ?? {
|
|
277
|
+
input: 0,
|
|
278
|
+
output: 0,
|
|
279
|
+
cacheRead: 0,
|
|
280
|
+
cacheWrite: 0,
|
|
281
|
+
totalTokens: 0,
|
|
282
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
283
|
+
},
|
|
284
|
+
errorMessage: options?.signal?.aborted
|
|
285
|
+
? 'Provider request cancelled'
|
|
286
|
+
: 'Provider required control failed',
|
|
287
|
+
};
|
|
288
|
+
output.push({
|
|
289
|
+
type: 'error',
|
|
290
|
+
reason: error.stopReason === 'aborted' ? 'aborted' : 'error',
|
|
291
|
+
error,
|
|
292
|
+
});
|
|
293
|
+
output.end();
|
|
294
|
+
if (!options?.signal?.aborted)
|
|
295
|
+
await fail();
|
|
296
|
+
}
|
|
297
|
+
})();
|
|
298
|
+
return output;
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
}
|