@ai-sdk/harness-acp 1.0.30 → 1.0.32

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 CHANGED
@@ -1,5 +1,30 @@
1
1
  # @ai-sdk/harness-acp
2
2
 
3
+ ## 1.0.32
4
+
5
+ ### Patch Changes
6
+
7
+ - 8961fde: feat(harness): allow changing `model` between turns via call options
8
+ - Updated dependencies [8961fde]
9
+ - Updated dependencies [eb59f2a]
10
+ - @ai-sdk/harness@1.0.94
11
+ - @ai-sdk/provider-utils@5.0.34
12
+
13
+ ## 1.0.31
14
+
15
+ ### Patch Changes
16
+
17
+ - 7608210: feat(harness): add `model` parameter to `HarnessAgent` instead of having each harness adapter support it on their own constructor functions
18
+ - f7bd978: chore(harness): update underlying adapter SDKs to their latest versions
19
+ - 14d4fc0: feat(harness): allow changing harness settings between turns via `prepareCall()` support on `HarnessAgent`
20
+ - Updated dependencies [cc9f6ce]
21
+ - Updated dependencies [7608210]
22
+ - Updated dependencies [6f8a2d7]
23
+ - Updated dependencies [14d4fc0]
24
+ - Updated dependencies [90192f1]
25
+ - @ai-sdk/harness@1.0.93
26
+ - @ai-sdk/provider-utils@5.0.33
27
+
3
28
  ## 1.0.30
4
29
 
5
30
  ### Patch Changes
package/README.md CHANGED
@@ -34,6 +34,10 @@ const codexACP = createACP({
34
34
  packageVersion: '1.1.4',
35
35
  },
36
36
  executable: 'codex-acp',
37
+ modelMapping: {
38
+ type: 'session-config-option',
39
+ path: 'model',
40
+ },
37
41
  credentialEnv: ['CODEX_API_KEY', 'OPENAI_API_KEY'],
38
42
  credentialBrokering: ({ env, sandboxEnv }) => {
39
43
  const environmentVariableName = env.CODEX_API_KEY
@@ -96,6 +100,13 @@ ACP supports only `permissionMode: 'allow-all'` because its
96
100
  restrictive modes enable Codex's internal sandbox. A bridge-backed ACP harness
97
101
  requires a sandbox with at least one exposed port.
98
102
 
103
+ `modelMapping` is required because ACP implementations expose different model
104
+ selection operations. Use `session-config-option` with the ACP configuration
105
+ option ID as `path`, or `session-model` with the JSON-RPC request property as
106
+ `path` for implementations such as Grok Build that use the legacy
107
+ `session/set_model` method. No model operation is sent when `HarnessAgent` has
108
+ no model and the deprecated `modelId` fallback is also unset.
109
+
99
110
  Use `instructionMapping` when the ACP implementation exposes a native system
100
111
  or developer prompt. A `session-meta` mapping writes `HarnessAgent`
101
112
  instructions below the ACP session request's `_meta` field. A
@@ -12,7 +12,7 @@ import {
12
12
  ListToolsRequestSchema,
13
13
  McpError
14
14
  } from "@modelcontextprotocol/sdk/types.js";
15
- var VERSION = true ? "1.0.30" : "0.0.0-test";
15
+ var VERSION = true ? "1.0.32" : "0.0.0-test";
16
16
  function createHostToolMCPServer({
17
17
  tools: tools2,
18
18
  revision = 1,
@@ -157,6 +157,7 @@ var ENV_TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
157
157
  var WS_OPEN = 1;
158
158
  async function runBridge(options) {
159
159
  const { bridgeType: bridgeType2, bridgeStateDir: bridgeStateDir2, onStart, onStop, onDestroy } = options;
160
+ const teardownGraceMs = options.turnTeardownGraceMs ?? 1e4;
160
161
  const expectedToken = options.token ?? procEnv.BRIDGE_CHANNEL_TOKEN ?? "";
161
162
  const bridgeWsPort = options.port ?? parseInt(procEnv.BRIDGE_WS_PORT ?? "0", 10);
162
163
  const bridgeMetaPath = `${bridgeStateDir2}/bridge-meta.json`;
@@ -173,6 +174,7 @@ async function runBridge(options) {
173
174
  let isFirstTurn = true;
174
175
  let turnAbort;
175
176
  let currentUserMessages;
177
+ let activeTurn;
176
178
  let debugConfig;
177
179
  let consoleCaptureInstalled = false;
178
180
  const envDebugEnabled = ENV_TRUTHY.has(
@@ -363,10 +365,28 @@ async function runBridge(options) {
363
365
  const handleInbound = async (msg, ws) => {
364
366
  switch (msg.type) {
365
367
  case "start": {
368
+ for (; ; ) {
369
+ const pendingTurn = activeTurn;
370
+ if (pendingTurn == null) break;
371
+ turnAbort?.abort();
372
+ currentUserMessages?.close(
373
+ new Error("A new bridge turn replaced the active turn.")
374
+ );
375
+ let graceTimer;
376
+ const settled = await Promise.race([
377
+ pendingTurn.then(() => true),
378
+ new Promise((resolve) => {
379
+ graceTimer = setTimeout(() => resolve(false), teardownGraceMs);
380
+ graceTimer.unref?.();
381
+ })
382
+ ]);
383
+ clearTimeout(graceTimer);
384
+ if (!settled) break;
385
+ }
386
+ let turnFinished;
387
+ const thisTurn = new Promise((resolve) => turnFinished = resolve);
388
+ activeTurn = thisTurn;
366
389
  activeSocket = ws;
367
- currentUserMessages?.close(
368
- new Error("A new bridge turn replaced the active turn.")
369
- );
370
390
  const firstTurn = isFirstTurn;
371
391
  isFirstTurn = false;
372
392
  eventLog = [];
@@ -423,8 +443,12 @@ async function runBridge(options) {
423
443
  if (currentUserMessages === userMessages) {
424
444
  currentUserMessages = void 0;
425
445
  }
426
- currentTurnState = "waiting";
427
- void writeBridgeMeta("waiting");
446
+ if (activeTurn === thisTurn) {
447
+ activeTurn = void 0;
448
+ currentTurnState = "waiting";
449
+ void writeBridgeMeta("waiting");
450
+ }
451
+ turnFinished();
428
452
  }
429
453
  return;
430
454
  }
@@ -607,7 +631,7 @@ function serialiseError(err) {
607
631
  }
608
632
 
609
633
  // src/v1/bridge/index.ts
610
- import * as acp4 from "@agentclientprotocol/sdk";
634
+ import * as acp5 from "@agentclientprotocol/sdk";
611
635
  import { spawn } from "child_process";
612
636
  import { readFile, writeFile as writeFile2 } from "fs/promises";
613
637
  import { fileURLToPath } from "url";
@@ -659,7 +683,8 @@ var bridgeConfigurationSchema = z.object({
659
683
  authentication: authenticationSchema.optional(),
660
684
  providerAuthentication: providerAuthenticationSchema.optional(),
661
685
  providerEnvironment: z.record(z.string(), z.string()).optional(),
662
- sessionMeta: serializableRecordSchema.optional()
686
+ sessionMeta: serializableRecordSchema.optional(),
687
+ clientCapabilities: serializableRecordSchema.optional()
663
688
  });
664
689
  async function readACPBridgeEnvironment({
665
690
  env
@@ -735,9 +760,13 @@ function createACPInitializeRequest({
735
760
  protocolVersion,
736
761
  clientApp,
737
762
  authentication,
763
+ clientCapabilities: configuredClientCapabilities,
738
764
  supportsBooleanSessionConfigOptions = false
739
765
  }) {
740
- let clientCapabilities = authentication?.clientCapabilities ?? {};
766
+ let clientCapabilities = mergeRecords({
767
+ left: authentication?.clientCapabilities ?? {},
768
+ right: configuredClientCapabilities ?? {}
769
+ });
741
770
  if (supportsBooleanSessionConfigOptions) {
742
771
  clientCapabilities = mergeRecords({
743
772
  left: clientCapabilities,
@@ -2881,8 +2910,34 @@ function unsupported({
2881
2910
  return new HarnessBridgeCapabilityUnsupportedError({ harnessId, message });
2882
2911
  }
2883
2912
 
2884
- // src/v1/bridge/recovered-session.ts
2913
+ // src/v1/bridge/model-mapping.ts
2885
2914
  import * as acp2 from "@agentclientprotocol/sdk";
2915
+ async function configureACPModel({
2916
+ agent,
2917
+ sessionId,
2918
+ model,
2919
+ mapping
2920
+ }) {
2921
+ if (model == null) return;
2922
+ if (mapping == null) {
2923
+ throw new Error("ACP model mapping is required when a model is set.");
2924
+ }
2925
+ if (mapping.type === "session-model") {
2926
+ await agent.request("session/set_model", {
2927
+ sessionId,
2928
+ [mapping.path]: model
2929
+ });
2930
+ return;
2931
+ }
2932
+ await agent.request(acp2.methods.agent.session.setConfigOption, {
2933
+ sessionId,
2934
+ configId: mapping.path,
2935
+ value: model
2936
+ });
2937
+ }
2938
+
2939
+ // src/v1/bridge/recovered-session.ts
2940
+ import * as acp3 from "@agentclientprotocol/sdk";
2886
2941
  function assertACPResumeCapability({
2887
2942
  initialization,
2888
2943
  harnessId
@@ -2914,7 +2969,7 @@ function createACPRecoveredSession({
2914
2969
  }
2915
2970
  updates.clearErrors();
2916
2971
  const response = agent.request(
2917
- acp2.methods.agent.session.prompt,
2972
+ acp3.methods.agent.session.prompt,
2918
2973
  { sessionId, prompt }
2919
2974
  );
2920
2975
  void response.then(
@@ -2935,7 +2990,7 @@ function createACPRecoveredSession({
2935
2990
  }
2936
2991
  updates.clearErrors();
2937
2992
  const response = agent.request(
2938
- acp2.methods.agent.session.prompt,
2993
+ acp3.methods.agent.session.prompt,
2939
2994
  { sessionId, prompt, _meta: meta }
2940
2995
  );
2941
2996
  void response.then(
@@ -3008,7 +3063,7 @@ function createACPRecoveredSessionUpdates() {
3008
3063
  }
3009
3064
 
3010
3065
  // src/v1/bridge/session-lifecycle.ts
3011
- import * as acp3 from "@agentclientprotocol/sdk";
3066
+ import * as acp4 from "@agentclientprotocol/sdk";
3012
3067
  function resolveACPSessionRestorationMethod({
3013
3068
  initialization,
3014
3069
  harnessId
@@ -3048,8 +3103,8 @@ async function restoreACPBridgeSession({
3048
3103
  mcpServers: [...mcpServers],
3049
3104
  ...meta == null ? {} : { _meta: meta }
3050
3105
  };
3051
- const response = method === "resume" ? await agent.request(acp3.methods.agent.session.resume, request) : await agent.request(
3052
- acp3.methods.agent.session.load,
3106
+ const response = method === "resume" ? await agent.request(acp4.methods.agent.session.resume, request) : await agent.request(
3107
+ acp4.methods.agent.session.load,
3053
3108
  request
3054
3109
  );
3055
3110
  discardCapturedHistory();
@@ -3201,6 +3256,12 @@ async function runTurn(start, turn) {
3201
3256
  });
3202
3257
  return;
3203
3258
  }
3259
+ await configureACPModel({
3260
+ agent: connection.agent,
3261
+ sessionId: activeSession.sessionId,
3262
+ model: start.model,
3263
+ mapping: start.modelMapping
3264
+ });
3204
3265
  let rejectCancellationFailure;
3205
3266
  const cancellationFailure = new Promise((_, reject) => {
3206
3267
  rejectCancellationFailure = reject;
@@ -3217,7 +3278,7 @@ async function runTurn(start, turn) {
3217
3278
  if (connection == null) {
3218
3279
  throw new Error("ACP connection closed before cancellation.");
3219
3280
  }
3220
- await connection.agent.notify(acp4.methods.agent.session.cancel, {
3281
+ await connection.agent.notify(acp5.methods.agent.session.cancel, {
3221
3282
  sessionId: activeSession.sessionId
3222
3283
  });
3223
3284
  } catch (error) {
@@ -3388,11 +3449,13 @@ async function ensureSession({
3388
3449
  instructions: start.instructions,
3389
3450
  instructionMapping: start.instructionMapping,
3390
3451
  sessionMeta: bridgeConfiguration.sessionMeta,
3391
- environment: createChildEnvironment({
3392
- launchEnv,
3393
- implementationDir,
3394
- privateHome: implementation.privateHome
3395
- })
3452
+ environment: {
3453
+ ...createChildEnvironment({
3454
+ launchEnv,
3455
+ implementationDir,
3456
+ privateHome: implementation.privateHome
3457
+ })
3458
+ }
3396
3459
  });
3397
3460
  child = spawn(
3398
3461
  `${implementationDir}/${implementation.executablePath}`,
@@ -3417,15 +3480,15 @@ async function ensureSession({
3417
3480
  const input = Writable.toWeb(child.stdin);
3418
3481
  const output = Readable.toWeb(child.stdout);
3419
3482
  const capturedStream = captureACPStream({
3420
- stream: acp4.ndJsonStream(input, output)
3483
+ stream: acp5.ndJsonStream(input, output)
3421
3484
  });
3422
3485
  streamCapture = capturedStream.capture;
3423
- connection = acp4.client({ name: clientApp.name }).onRequest(
3424
- acp4.methods.client.session.requestPermission,
3486
+ connection = acp5.client({ name: clientApp.name }).onRequest(
3487
+ acp5.methods.client.session.requestPermission,
3425
3488
  ({ params }) => activePermissionController?.requestPermission(params) ?? {
3426
3489
  outcome: { outcome: "cancelled" }
3427
3490
  }
3428
- ).onNotification(acp4.methods.client.session.update, ({ params }) => {
3491
+ ).onNotification(acp5.methods.client.session.update, ({ params }) => {
3429
3492
  if (params.sessionId !== recoveredSessionId) return;
3430
3493
  if (historicalUpdatesSuppressed) return;
3431
3494
  recoveredSessionUpdates?.enqueue({
@@ -3435,18 +3498,19 @@ async function ensureSession({
3435
3498
  });
3436
3499
  }).connect(capturedStream.stream);
3437
3500
  const initializeRequest = createACPInitializeRequest({
3438
- protocolVersion: acp4.PROTOCOL_VERSION,
3501
+ protocolVersion: acp5.PROTOCOL_VERSION,
3439
3502
  clientApp,
3440
3503
  authentication,
3504
+ clientCapabilities: bridgeConfiguration.clientCapabilities,
3441
3505
  supportsBooleanSessionConfigOptions: Object.values(
3442
3506
  start.permissionModeMapping ?? {}
3443
3507
  ).some(
3444
3508
  (target) => target?.type === "session-config-option" && typeof target.value === "boolean"
3445
3509
  )
3446
3510
  });
3447
- const initialization = await connection.agent.request(acp4.methods.agent.initialize, initializeRequest);
3511
+ const initialization = await connection.agent.request(acp5.methods.agent.initialize, initializeRequest);
3448
3512
  validateACPProtocolVersion({
3449
- requested: acp4.PROTOCOL_VERSION,
3513
+ requested: acp5.PROTOCOL_VERSION,
3450
3514
  initialization
3451
3515
  });
3452
3516
  if (authentication != null) {
@@ -3494,7 +3558,7 @@ async function ensureSession({
3494
3558
  assertACPResumeCapability({ initialization, harnessId: bridgeType });
3495
3559
  recoveredSessionId = start.recoveryMode.acpSessionId;
3496
3560
  recoveredSessionUpdates = createACPRecoveredSessionUpdates();
3497
- const resumeResponse = await connection.agent.request(acp4.methods.agent.session.resume, {
3561
+ const resumeResponse = await connection.agent.request(acp5.methods.agent.session.resume, {
3498
3562
  sessionId: recoveredSessionId,
3499
3563
  cwd: workDir,
3500
3564
  mcpServers,
@@ -3616,7 +3680,7 @@ async function authenticate({
3616
3680
  initialization,
3617
3681
  methodId: authentication.methodId
3618
3682
  });
3619
- await agent.request(acp4.methods.agent.authenticate, {
3683
+ await agent.request(acp5.methods.agent.authenticate, {
3620
3684
  methodId: authentication.methodId,
3621
3685
  ...authentication.meta == null ? {} : { _meta: authentication.meta }
3622
3686
  });
@@ -3718,7 +3782,7 @@ function promptActiveSession({
3718
3782
  }
3719
3783
  updates.clearErrors();
3720
3784
  const response = agent.request(
3721
- acp4.methods.agent.session.prompt,
3785
+ acp5.methods.agent.session.prompt,
3722
3786
  {
3723
3787
  sessionId: session2.sessionId,
3724
3788
  prompt,