@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 +16 -0
- package/README.md +13 -0
- package/dist/bridge/host-tool-mcp.mjs +1 -1
- package/dist/bridge/index.mjs +103 -12
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts +20 -1
- package/dist/index.js +41 -6
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/acp-harness.ts +1 -0
- package/src/index.ts +1 -0
- package/src/v1/acp-v1-bridge-protocol.ts +21 -1
- package/src/v1/acp-v1-harness.ts +37 -1
- package/src/v1/acp-v1-settings.ts +21 -0
- package/src/v1/acp-v1-turn-start-config.ts +4 -0
- package/src/v1/bridge/index.ts +14 -6
- package/src/v1/bridge/instruction-mapping.ts +132 -0
- package/src/v1/index.ts +1 -0
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.
|
|
15
|
+
var VERSION = true ? "1.0.4" : "0.0.0-test";
|
|
16
16
|
function createHostToolMCPServer({
|
|
17
17
|
tools: tools2,
|
|
18
18
|
revision = 1,
|
package/dist/bridge/index.mjs
CHANGED
|
@@ -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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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:
|
|
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
|
-
...
|
|
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:
|
|
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
|
-
...
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 {
|