@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
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: providerRoute(c.provider === undefined ? 'offline' : String(c.provider), c.model, c.max_output_tokens, c.workspace_id),
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
- pi.registerTool({
27
- name: 'salaam_probe',
28
- label: 'Salaam readiness probe',
29
- description: 'Verify the managed tool gate without external effects.',
30
- parameters: Type.Object({ nested: Type.Optional(Type.Boolean()) }),
31
- annotations: { readOnlyHint: true, openWorldHint: false },
32
- async execute(toolCallId, params, signal, _update, ctx) {
33
- if (params.nested) {
34
- const outcome = await ctx.executeTool('salaam_probe', {}, { signal });
35
- if (outcome.isError) {
36
- await fail();
37
- throw new Error('Nested managed probe failed');
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
- return { ...outcome.result, isError: outcome.isError };
40
- }
41
- await bridge('tool', { tool: 'salaam_probe', tool_call_id: toolCallId });
42
- return {
43
- content: [{ type: 'text', text: 'managed probe granted' }],
44
- details: { granted: true },
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.coding && !c.child) {
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
- pi.registerTool({
201
- name: 'salaam_files',
202
- label: 'Guarded workspace files',
203
- 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.',
204
- parameters: Type.Object({
205
- action: Type.Union(['inspect', 'list', 'search', 'read', 'patch'].map((value) => Type.Literal(value))),
206
- path: Type.Optional(Type.String({ maxLength: 512 })),
207
- query: Type.Optional(Type.String({ maxLength: 256 })),
208
- offset: Type.Optional(Type.Integer({ minimum: 0 })),
209
- expected_revision: Type.Optional(Type.Integer({ minimum: 0 })),
210
- expected_hash: Type.Optional(Type.Union([Type.String(), Type.Null()])),
211
- content: Type.Optional(Type.Union([Type.String({ maxLength: 262144 }), Type.Null()])),
212
- }),
213
- async execute(id, params) {
214
- try {
215
- const result = await bridge('coding.files', {
216
- tool_call_id: id,
217
- args: params,
218
- });
219
- return {
220
- content: [{ type: 'text', text: JSON.stringify(result) }],
221
- details: result,
222
- isError: params.action === 'patch' && result.state !== 'applied',
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
- return {
231
- content: [{ type: 'text', text: error.message }],
232
- details: { error: error.code },
233
- isError: true,
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 === 'anthropic')
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
+ }