@ai-sdk/harness-acp 1.0.2 → 1.0.4

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,21 @@
1
1
  # @ai-sdk/harness-acp
2
2
 
3
+ ## 1.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [7fbfc6d]
8
+ - @ai-sdk/provider-utils@5.0.27
9
+ - @ai-sdk/harness@1.0.67
10
+
11
+ ## 1.0.3
12
+
13
+ ### Patch Changes
14
+
15
+ - 9ee30bf: fix(harness): pass instructions appended to system / developer prompt instead of using the workaround of first user prompt
16
+ - Updated dependencies [9ee30bf]
17
+ - @ai-sdk/harness@1.0.66
18
+
3
19
  ## 1.0.2
4
20
 
5
21
  ### Patch Changes
package/README.md CHANGED
@@ -34,6 +34,11 @@ const codexACP = createACP({
34
34
  },
35
35
  executable: 'codex-acp',
36
36
  forwardEnv: ['CODEX_API_KEY', 'OPENAI_API_KEY'],
37
+ instructionMapping: {
38
+ type: 'launch-env-json',
39
+ variable: 'CODEX_CONFIG',
40
+ path: ['developer_instructions'],
41
+ },
37
42
  permissionModeMapping: {
38
43
  'allow-reads': null,
39
44
  'allow-edits': null,
@@ -69,3 +74,11 @@ resolves the value when the ACP process starts and does not store it in the
69
74
  profile. Codex ACP supports only `permissionMode: 'allow-all'` because its
70
75
  restrictive modes enable Codex's internal sandbox. A bridge-backed ACP harness
71
76
  requires a sandbox with at least one exposed port.
77
+
78
+ Use `instructionMapping` when the ACP implementation exposes a native system
79
+ or developer prompt. A `session-meta` mapping writes `HarnessAgent`
80
+ instructions below the ACP session request's `_meta` field. A
81
+ `launch-env-json` mapping merges them into a JSON environment variable before
82
+ the implementation starts. Without a mapping, the adapter preserves its
83
+ backward-compatible behavior and prepends instructions to the first user
84
+ prompt.
@@ -12,7 +12,7 @@ import {
12
12
  ListToolsRequestSchema,
13
13
  McpError
14
14
  } from "@modelcontextprotocol/sdk/types.js";
15
- var VERSION = true ? "1.0.2" : "0.0.0-test";
15
+ var VERSION = true ? "1.0.4" : "0.0.0-test";
16
16
  function createHostToolMCPServer({
17
17
  tools: tools2,
18
18
  revision = 1,
@@ -2024,6 +2024,90 @@ function createEmitStreamEvent({
2024
2024
  };
2025
2025
  }
2026
2026
 
2027
+ // src/v1/bridge/instruction-mapping.ts
2028
+ import { z as z2 } from "zod/v4";
2029
+ var UNSAFE_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
2030
+ var serializableRecordSchema2 = z2.record(z2.string(), z2.json());
2031
+ async function resolveACPInstructionConfiguration({
2032
+ instructions,
2033
+ instructionMapping,
2034
+ sessionMeta,
2035
+ environment
2036
+ }) {
2037
+ const resolvedEnvironment = { ...environment };
2038
+ if (instructionMapping == null || instructions == null || instructions.length === 0) {
2039
+ return { sessionMeta, environment: resolvedEnvironment };
2040
+ }
2041
+ assertSafePath({ path: instructionMapping.path });
2042
+ if (instructionMapping.type === "session-meta") {
2043
+ return {
2044
+ sessionMeta: setStringAtPath({
2045
+ record: sessionMeta ?? {},
2046
+ path: instructionMapping.path,
2047
+ value: instructions
2048
+ }),
2049
+ environment: resolvedEnvironment
2050
+ };
2051
+ }
2052
+ const serialized = resolvedEnvironment[instructionMapping.variable];
2053
+ let configuration = {};
2054
+ if (serialized != null && serialized.length > 0) {
2055
+ configuration = parseSerializableRecord({
2056
+ serialized,
2057
+ variable: instructionMapping.variable
2058
+ });
2059
+ }
2060
+ resolvedEnvironment[instructionMapping.variable] = JSON.stringify(
2061
+ setStringAtPath({
2062
+ record: configuration,
2063
+ path: instructionMapping.path,
2064
+ value: instructions
2065
+ })
2066
+ );
2067
+ return { sessionMeta, environment: resolvedEnvironment };
2068
+ }
2069
+ function parseSerializableRecord({
2070
+ serialized,
2071
+ variable
2072
+ }) {
2073
+ try {
2074
+ const result = serializableRecordSchema2.safeParse(JSON.parse(serialized));
2075
+ if (result.success) return result.data;
2076
+ } catch {
2077
+ }
2078
+ throw new Error(
2079
+ `ACP instruction mapping environment variable ${JSON.stringify(variable)} must contain a JSON object.`
2080
+ );
2081
+ }
2082
+ function setStringAtPath({
2083
+ record,
2084
+ path,
2085
+ value
2086
+ }) {
2087
+ const [key, ...remainingPath] = path;
2088
+ if (key == null) return { ...record };
2089
+ return {
2090
+ ...record,
2091
+ [key]: remainingPath.length === 0 ? value : setStringAtPath({
2092
+ record: isRecord5(record[key]) ? record[key] : {},
2093
+ path: remainingPath,
2094
+ value
2095
+ })
2096
+ };
2097
+ }
2098
+ function assertSafePath({ path }) {
2099
+ if (path.length === 0 || path.some(
2100
+ (segment) => segment.length === 0 || UNSAFE_PATH_SEGMENTS.has(segment)
2101
+ )) {
2102
+ throw new Error(
2103
+ "ACP instruction mapping path must contain only safe, non-empty property names."
2104
+ );
2105
+ }
2106
+ }
2107
+ function isRecord5(value) {
2108
+ return value != null && typeof value === "object" && !Array.isArray(value);
2109
+ }
2110
+
2027
2111
  // src/v1/bridge/host-tool-relay.ts
2028
2112
  import { randomBytes, timingSafeEqual } from "crypto";
2029
2113
  import { createServer } from "http";
@@ -2150,7 +2234,7 @@ async function handleCatalogNext({
2150
2234
  body,
2151
2235
  state
2152
2236
  }) {
2153
- if (!isRecord5(body) || !Number.isSafeInteger(body.afterRevision) || body.afterRevision < 0) {
2237
+ if (!isRecord6(body) || !Number.isSafeInteger(body.afterRevision) || body.afterRevision < 0) {
2154
2238
  throw new RelayRequestError({
2155
2239
  status: 400,
2156
2240
  message: "Invalid host tool catalog poll request."
@@ -2169,7 +2253,7 @@ function handleCatalogSeen({
2169
2253
  body,
2170
2254
  state
2171
2255
  }) {
2172
- if (!isRecord5(body) || !Number.isSafeInteger(body.revision) || body.revision < 1 || body.revision > state.revision) {
2256
+ if (!isRecord6(body) || !Number.isSafeInteger(body.revision) || body.revision < 1 || body.revision > state.revision) {
2173
2257
  throw new RelayRequestError({
2174
2258
  status: 400,
2175
2259
  message: "Invalid host tool catalog acknowledgment."
@@ -2195,7 +2279,7 @@ async function handleInvocation({
2195
2279
  message: "No ACP prompt turn is active."
2196
2280
  });
2197
2281
  }
2198
- if (!isRecord5(body) || typeof body.requestId !== "string" || typeof body.toolName !== "string" || !isRecord5(body.input) || !Number.isSafeInteger(body.catalogRevision)) {
2282
+ if (!isRecord6(body) || typeof body.requestId !== "string" || typeof body.toolName !== "string" || !isRecord6(body.input) || !Number.isSafeInteger(body.catalogRevision)) {
2199
2283
  throw new RelayRequestError({
2200
2284
  status: 400,
2201
2285
  message: "Invalid host tool relay request."
@@ -2311,7 +2395,7 @@ function canonicalizeJSON2({ value }) {
2311
2395
  if (Array.isArray(value)) {
2312
2396
  return value.map((item) => canonicalizeJSON2({ value: item }));
2313
2397
  }
2314
- if (!isRecord5(value)) return value;
2398
+ if (!isRecord6(value)) return value;
2315
2399
  return Object.fromEntries(
2316
2400
  Object.keys(value).sort().filter((key) => value[key] !== void 0).map((key) => [key, canonicalizeJSON2({ value: value[key] })])
2317
2401
  );
@@ -2369,7 +2453,7 @@ function closeServer({ server }) {
2369
2453
  });
2370
2454
  });
2371
2455
  }
2372
- function isRecord5(value) {
2456
+ function isRecord6(value) {
2373
2457
  return value != null && typeof value === "object" && !Array.isArray(value);
2374
2458
  }
2375
2459
  var RelayRequestError = class extends Error {
@@ -3024,6 +3108,7 @@ async function ensureSession({
3024
3108
  authentication: bridgeConfiguration.authentication,
3025
3109
  providerAuthentication: bridgeConfiguration.providerAuthentication,
3026
3110
  sessionMeta: bridgeConfiguration.sessionMeta,
3111
+ instructionMapping: start.instructionMapping,
3027
3112
  permissionMode: start.permissionMode,
3028
3113
  permissionModeMapping: start.permissionModeMapping,
3029
3114
  mcpServers: start.mcpServers
@@ -3061,12 +3146,18 @@ async function ensureSession({
3061
3146
  providerAuthentication: bridgeConfiguration.providerAuthentication,
3062
3147
  gateway
3063
3148
  });
3149
+ const instructionConfiguration = await resolveACPInstructionConfiguration({
3150
+ instructions: start.instructions,
3151
+ instructionMapping: start.instructionMapping,
3152
+ sessionMeta: bridgeConfiguration.sessionMeta,
3153
+ environment: createChildEnvironment({ launchEnv })
3154
+ });
3064
3155
  child = spawn(
3065
3156
  `${implementationDir}/node_modules/.bin/${implementation.executable}`,
3066
3157
  [...implementation.args],
3067
3158
  {
3068
3159
  cwd: workDir,
3069
- env: createChildEnvironment({ launchEnv }),
3160
+ env: instructionConfiguration.environment,
3070
3161
  shell: false,
3071
3162
  stdio: ["pipe", "pipe", "pipe"]
3072
3163
  }
@@ -3165,7 +3256,7 @@ async function ensureSession({
3165
3256
  sessionId: recoveredSessionId,
3166
3257
  cwd: workDir,
3167
3258
  mcpServers,
3168
- ...bridgeConfiguration.sessionMeta == null ? {} : { _meta: bridgeConfiguration.sessionMeta }
3259
+ ...instructionConfiguration.sessionMeta == null ? {} : { _meta: instructionConfiguration.sessionMeta }
3169
3260
  });
3170
3261
  createdSession = createACPRecoveredSession({
3171
3262
  agent: connection.agent,
@@ -3182,7 +3273,7 @@ async function ensureSession({
3182
3273
  sessionId: recoveredSessionId,
3183
3274
  cwd: workDir,
3184
3275
  mcpServers,
3185
- meta: bridgeConfiguration.sessionMeta,
3276
+ meta: instructionConfiguration.sessionMeta,
3186
3277
  harnessId: bridgeType,
3187
3278
  setHistoricalUpdatesSuppressed: ({ suppressed }) => {
3188
3279
  historicalUpdatesSuppressed = suppressed;
@@ -3202,7 +3293,7 @@ async function ensureSession({
3202
3293
  createdSession = await connection.agent.buildSession({
3203
3294
  cwd: workDir,
3204
3295
  mcpServers,
3205
- ...bridgeConfiguration.sessionMeta == null ? {} : { _meta: bridgeConfiguration.sessionMeta }
3296
+ ...instructionConfiguration.sessionMeta == null ? {} : { _meta: instructionConfiguration.sessionMeta }
3206
3297
  }).start();
3207
3298
  }
3208
3299
  try {
@@ -3243,7 +3334,7 @@ function createExternalMcpServers({
3243
3334
  }) {
3244
3335
  if (mcpServers == null) return [];
3245
3336
  return Object.entries(mcpServers).map(([name2, value]) => {
3246
- if (!isRecord6(value)) {
3337
+ if (!isRecord7(value)) {
3247
3338
  throw new Error(
3248
3339
  `ACP MCP server ${JSON.stringify(name2)} must be configured with an object value.`
3249
3340
  );
@@ -3339,7 +3430,7 @@ function createChildEnvironment({
3339
3430
  ...launchEnv
3340
3431
  };
3341
3432
  }
3342
- function isRecord6(value) {
3433
+ function isRecord7(value) {
3343
3434
  return value != null && typeof value === "object" && !Array.isArray(value);
3344
3435
  }
3345
3436
  async function readImplementationDescriptor({
@@ -3349,7 +3440,7 @@ async function readImplementationDescriptor({
3349
3440
  const value = await new Response(text, {
3350
3441
  headers: { "content-type": "application/json" }
3351
3442
  }).json();
3352
- if (!isRecord6(value) || typeof value.executable !== "string" || !Array.isArray(value.args) || !value.args.every((item) => typeof item === "string") || !Array.isArray(value.envKeys) || !value.envKeys.every((item) => typeof item === "string")) {
3443
+ if (!isRecord7(value) || typeof value.executable !== "string" || !Array.isArray(value.args) || !value.args.every((item) => typeof item === "string") || !Array.isArray(value.envKeys) || !value.envKeys.every((item) => typeof item === "string")) {
3353
3444
  throw new Error("Invalid ACP implementation descriptor.");
3354
3445
  }
3355
3446
  return {