@tangle-network/agent-profile-materialize 1.1.7 → 1.1.8
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/dist/index.d.ts +1226 -29
- package/dist/index.js +4987 -23
- package/package.json +7 -5
- package/dist/candidate-types.d.ts +0 -36
- package/dist/candidate-types.js +0 -1
- package/dist/candidate.d.ts +0 -9
- package/dist/candidate.js +0 -283
- package/dist/commands.d.ts +0 -2
- package/dist/commands.js +0 -32
- package/dist/control-maps.d.ts +0 -58
- package/dist/control-maps.js +0 -277
- package/dist/execution-controls.d.ts +0 -5
- package/dist/execution-controls.js +0 -199
- package/dist/harness-prompt.d.ts +0 -22
- package/dist/harness-prompt.js +0 -64
- package/dist/harness.d.ts +0 -131
- package/dist/harness.js +0 -416
- package/dist/hooks.d.ts +0 -6
- package/dist/hooks.js +0 -250
- package/dist/managed-workspace-bind.d.ts +0 -4
- package/dist/managed-workspace-bind.js +0 -106
- package/dist/managed-workspace-stage.d.ts +0 -25
- package/dist/managed-workspace-stage.js +0 -199
- package/dist/managed-workspace-types.d.ts +0 -38
- package/dist/managed-workspace-types.js +0 -1
- package/dist/managed-workspace.d.ts +0 -31
- package/dist/managed-workspace.js +0 -271
- package/dist/mcp-config-ownership.d.ts +0 -13
- package/dist/mcp-config-ownership.js +0 -79
- package/dist/mcp-opencode.d.ts +0 -25
- package/dist/mcp-opencode.js +0 -52
- package/dist/mcp-timeouts.d.ts +0 -7
- package/dist/mcp-timeouts.js +0 -48
- package/dist/mcp.d.ts +0 -4
- package/dist/mcp.js +0 -246
- package/dist/model-id.d.ts +0 -55
- package/dist/model-id.js +0 -67
- package/dist/pi-models-config.d.ts +0 -32
- package/dist/pi-models-config.js +0 -65
- package/dist/plan-application.d.ts +0 -10
- package/dist/plan-application.js +0 -190
- package/dist/plan-digest.d.ts +0 -18
- package/dist/plan-digest.js +0 -116
- package/dist/plan-json.d.ts +0 -13
- package/dist/plan-json.js +0 -91
- package/dist/plan-normalize.d.ts +0 -12
- package/dist/plan-normalize.js +0 -168
- package/dist/plan-prompt.d.ts +0 -38
- package/dist/plan-prompt.js +0 -49
- package/dist/plan-support.d.ts +0 -9
- package/dist/plan-support.js +0 -22
- package/dist/plan-types.d.ts +0 -341
- package/dist/plan-types.js +0 -105
- package/dist/prime-agent-dir.d.ts +0 -64
- package/dist/prime-agent-dir.js +0 -173
- package/dist/prime-fork.d.ts +0 -155
- package/dist/prime-fork.js +0 -262
- package/dist/prime-profile.d.ts +0 -88
- package/dist/prime-profile.js +0 -297
- package/dist/profile-bundle-cache.d.ts +0 -37
- package/dist/profile-bundle-cache.js +0 -320
- package/dist/profile-bundle-key.d.ts +0 -69
- package/dist/profile-bundle-key.js +0 -62
- package/dist/profile-bundle-lru.d.ts +0 -56
- package/dist/profile-bundle-lru.js +0 -220
- package/dist/profile-materializer.d.ts +0 -24
- package/dist/profile-materializer.js +0 -114
- package/dist/profile-plan.d.ts +0 -16
- package/dist/profile-plan.js +0 -90
- package/dist/profile-prompt.d.ts +0 -4
- package/dist/profile-prompt.js +0 -107
- package/dist/profile-support.d.ts +0 -8
- package/dist/profile-support.js +0 -6
- package/dist/resource-lowering.d.ts +0 -6
- package/dist/resource-lowering.js +0 -103
- package/dist/resource-resolution.d.ts +0 -27
- package/dist/resource-resolution.js +0 -265
- package/dist/secret-concurrent-revision.d.ts +0 -30
- package/dist/secret-concurrent-revision.js +0 -249
- package/dist/secret-session-view.d.ts +0 -25
- package/dist/secret-session-view.js +0 -166
- package/dist/subagents.d.ts +0 -6
- package/dist/subagents.js +0 -137
- package/dist/tool-bin-path.d.ts +0 -23
- package/dist/tool-bin-path.js +0 -61
- package/dist/workspace-apply-files.d.ts +0 -57
- package/dist/workspace-apply-files.js +0 -193
- package/dist/workspace-apply-publish.d.ts +0 -22
- package/dist/workspace-apply-publish.js +0 -178
- package/dist/workspace-apply.d.ts +0 -11
- package/dist/workspace-apply.js +0 -124
- package/dist/workspace-files.d.ts +0 -8
- package/dist/workspace-files.js +0 -46
- package/dist/workspace-launch.d.ts +0 -3
- package/dist/workspace-launch.js +0 -13
package/dist/mcp.js
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
export function materializeMcp(h, mcp, plan, add, unsupported) {
|
|
2
|
-
switch (h) {
|
|
3
|
-
case "claude-code":
|
|
4
|
-
materializeClaudeMcp(h, mcp, plan, add, unsupported);
|
|
5
|
-
break;
|
|
6
|
-
case "nanoclaw":
|
|
7
|
-
unsupported("mcp", "nanoclaw MCP must be injected through options.mcpServers by its SDK adapter");
|
|
8
|
-
break;
|
|
9
|
-
case "codex":
|
|
10
|
-
materializeCodexMcp(h, mcp, plan, unsupported);
|
|
11
|
-
break;
|
|
12
|
-
case "opencode":
|
|
13
|
-
// opencode.json "mcp" block (local|remote).
|
|
14
|
-
add(OPENCODE_CONFIG_FILE, serializeWorkspacePlanJson({
|
|
15
|
-
$schema: OPENCODE_CONFIG_SCHEMA,
|
|
16
|
-
mcp: openCodeMcpConfig(mcp, mcpToolTimeouts(mcp, h, unsupported)),
|
|
17
|
-
}));
|
|
18
|
-
break;
|
|
19
|
-
case "gemini":
|
|
20
|
-
materializeGeminiMcp(mcp, add);
|
|
21
|
-
break;
|
|
22
|
-
case "kimi-code": {
|
|
23
|
-
// kimi-code 2.x auto-discovers project MCP config from
|
|
24
|
-
// <cwd>/.kimi-code/mcp.json (2.3.0 dist/main.mjs:
|
|
25
|
-
// `project: join(input.cwd, ".kimi-code", "mcp.json")`; live-probed on
|
|
26
|
-
// 2.2.0). The previous lowering — .kimi/mcp.json plus a
|
|
27
|
-
// --mcp-config-file flag — targeted the legacy Python kimi-cli:
|
|
28
|
-
// kimi-code 2.x exits 1 with `error: unknown option '--mcp-config-file'`
|
|
29
|
-
// and never reads that file. Project MCP is gated behind workspace
|
|
30
|
-
// trust; an unattended executor exports KIMI_CODE_TRUST_WORKSPACE=1
|
|
31
|
-
// itself — the plan never forces trust, that posture is the launcher's.
|
|
32
|
-
const rel = ".kimi-code/mcp.json";
|
|
33
|
-
const servers = Object.fromEntries(Object.entries(mcp).map(([name, server]) => [
|
|
34
|
-
name,
|
|
35
|
-
kimiMcpServer(server),
|
|
36
|
-
]));
|
|
37
|
-
add(rel, serializeWorkspacePlanJson({ mcpServers: servers }));
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
case "pi":
|
|
41
|
-
materializePiMcp(mcp, plan, add);
|
|
42
|
-
break;
|
|
43
|
-
case "prime":
|
|
44
|
-
// The fork's MCP config surface (`mcpServers` in <agentDir>/settings.json,
|
|
45
|
-
// fork settings-manager.ts:98-120,219) IS read at startup, but a
|
|
46
|
-
// user-declared server never becomes model-visible: the host McpManager
|
|
47
|
-
// only registers OAuth providers and serves mcp.config/refresh
|
|
48
|
-
// host-requests (fork core/mcp/mcp-manager.ts:62-124,156-181), skips
|
|
49
|
-
// stdio servers entirely (mcp-manager.ts:73), and nothing generates a
|
|
50
|
-
// skill or prompt section for it — materializing profile.mcp would be a
|
|
51
|
-
// silently dropped control.
|
|
52
|
-
unsupported("mcp", "prime: settings.json mcpServers never becomes model-visible (fork mcp-manager.ts:62-124 " +
|
|
53
|
-
"registers OAuth providers only and skips stdio servers) — model-visible MCP is exactly " +
|
|
54
|
-
"the fork's built-in catalog skills");
|
|
55
|
-
break;
|
|
56
|
-
case "hermes":
|
|
57
|
-
unsupported("mcp", "hermes MCP is global config.yaml (mcp_servers), no cwd discovery — write to the hermes global config or pass a config flag");
|
|
58
|
-
break;
|
|
59
|
-
case "openclaw":
|
|
60
|
-
unsupported("mcp", "openclaw MCP is central ~/.openclaw/openclaw.json (mcp.servers), no cwd discovery");
|
|
61
|
-
break;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
function materializeClaudeMcp(h, mcp, plan, add, unsupported) {
|
|
65
|
-
// Claude applies one MCP tool-call timeout to every server, so the
|
|
66
|
-
// longest declared one wins and is what each declaring server is told.
|
|
67
|
-
const timeouts = mcpToolTimeouts(mcp, h, unsupported);
|
|
68
|
-
const toolTimeoutMs = timeouts.size > 0 ? Math.max(...timeouts.values()) : undefined;
|
|
69
|
-
if (toolTimeoutMs !== undefined) {
|
|
70
|
-
setPlanEnvironment(plan, "MCP_TOOL_TIMEOUT", String(toolTimeoutMs));
|
|
71
|
-
}
|
|
72
|
-
const servers = {};
|
|
73
|
-
for (const [name, server] of Object.entries(mcp)) {
|
|
74
|
-
if (server.url) {
|
|
75
|
-
const headers = withAppliedToolTimeout(server.headers, timeouts.has(name) ? toolTimeoutMs : undefined);
|
|
76
|
-
servers[name] = {
|
|
77
|
-
type: server.transport ?? "http",
|
|
78
|
-
url: server.url,
|
|
79
|
-
...(headers ? { headers } : {}),
|
|
80
|
-
};
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
if (server.cwd !== undefined) {
|
|
84
|
-
unsupported("mcp", `${h}: local MCP server "${name}" does not support a cwd field`);
|
|
85
|
-
}
|
|
86
|
-
servers[name] = {
|
|
87
|
-
type: "stdio",
|
|
88
|
-
command: server.command,
|
|
89
|
-
...(server.args ? { args: server.args } : {}),
|
|
90
|
-
...(server.env ? { env: server.env } : {}),
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
const relPath = ".tangle/claude-mcp.json";
|
|
94
|
-
add(relPath, serializeWorkspacePlanJson({ mcpServers: servers }));
|
|
95
|
-
plan.flags.push("--mcp-config", relPath, "--strict-mcp-config");
|
|
96
|
-
}
|
|
97
|
-
function materializeCodexMcp(h, mcp, plan, unsupported) {
|
|
98
|
-
const timeouts = mcpToolTimeouts(mcp, h, unsupported);
|
|
99
|
-
const servers = [];
|
|
100
|
-
for (const [name, server] of Object.entries(mcp).sort(([left], [right]) => compareStrings(left, right))) {
|
|
101
|
-
if (server.url && server.transport === "sse") {
|
|
102
|
-
unsupported("mcp", `codex: remote MCP server "${name}" uses unsupported SSE transport`);
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
servers.push(concatWorkspacePlanArguments(`${tomlStr(name)} = `, codexMcpServerToml(name, server, plan, timeouts.get(name))));
|
|
106
|
-
}
|
|
107
|
-
if (servers.length > 0) {
|
|
108
|
-
// Runtime overrides do not require persisted project trust. Header values
|
|
109
|
-
// ride environment variables so credentials never appear in argv.
|
|
110
|
-
plan.flags.push("-c", concatWorkspacePlanArguments("mcp_servers={ ", joinWorkspacePlanArguments(servers, ", "), " }"));
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
function materializeGeminiMcp(mcp, add) {
|
|
114
|
-
const servers = {};
|
|
115
|
-
for (const [name, server] of Object.entries(mcp)) {
|
|
116
|
-
if (server.url) {
|
|
117
|
-
servers[name] = {
|
|
118
|
-
[server.transport === "sse" ? "url" : "httpUrl"]: server.url,
|
|
119
|
-
...(server.headers ? { headers: server.headers } : {}),
|
|
120
|
-
};
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
servers[name] = {
|
|
124
|
-
command: server.command,
|
|
125
|
-
...(server.args ? { args: server.args } : {}),
|
|
126
|
-
...(server.env ? { env: server.env } : {}),
|
|
127
|
-
...(server.cwd ? { cwd: server.cwd } : {}),
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
add(".gemini/settings.json", serializeWorkspacePlanJson({ mcpServers: servers }));
|
|
131
|
-
}
|
|
132
|
-
function materializePiMcp(mcp, plan, add) {
|
|
133
|
-
// pi ships NO MCP. It arrives from the `pi-mcp-adapter` extension, which discovers
|
|
134
|
-
// `<cwd>/.pi/mcp.json` (adapter 2.17.0 config.ts:17) and also registers a `--mcp-config`
|
|
135
|
-
// flag (index.ts:252). The flag is registered BY the extension, so passing it when the
|
|
136
|
-
// extension is absent is not a fallback — it is an unknown flag.
|
|
137
|
-
//
|
|
138
|
-
// This was previously marked unsupported because a workspace file cannot install an
|
|
139
|
-
// extension. That reasoning was right about the constraint and wrong about the conclusion:
|
|
140
|
-
// the file half is perfectly materializable, and the half that is not becomes a declared
|
|
141
|
-
// requirement the executor enforces. Marking the whole dimension unsupported pushed both
|
|
142
|
-
// halves onto every consumer, and two of them independently rebuilt it.
|
|
143
|
-
const rel = ".pi/mcp.json";
|
|
144
|
-
const servers = Object.fromEntries(Object.entries(mcp).map(([name, server]) => [
|
|
145
|
-
name,
|
|
146
|
-
// `directTools` promotes this server's verbs to first-class pi tools. Without it the
|
|
147
|
-
// agent gets one generic `mcp` tool and must connect, then describe, then call — three
|
|
148
|
-
// round trips of discovery before it can do anything. Per-server, because the adapter
|
|
149
|
-
// lets a per-server value override the global `settings.directTools` (direct-tools.ts:135).
|
|
150
|
-
{ ...piMcpServer(server), directTools: true },
|
|
151
|
-
]));
|
|
152
|
-
// serializeWorkspacePlanJson, NOT JSON.stringify: it unwraps public config values to their
|
|
153
|
-
// bytes and lifts any secret-ref into a tracked slot the applier resolves. Stringifying
|
|
154
|
-
// straight would write `{"kind":"public","value":"..."}` into the file as an argv entry.
|
|
155
|
-
add(rel, serializeWorkspacePlanJson({ mcpServers: servers }));
|
|
156
|
-
plan.requires?.push({
|
|
157
|
-
kind: "harness-extension",
|
|
158
|
-
dimension: "mcp",
|
|
159
|
-
id: "npm:pi-mcp-adapter",
|
|
160
|
-
reason: "pi has no built-in MCP; without this extension the servers in .pi/mcp.json are never read and the agent starts with none of their tools",
|
|
161
|
-
remedy: "pi install npm:pi-mcp-adapter",
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
function tomlInlineStringTable(values) {
|
|
165
|
-
return `{ ${Object.entries(values)
|
|
166
|
-
.sort(([left], [right]) => compareStrings(left, right))
|
|
167
|
-
.map(([key, value]) => `${tomlStr(key)} = ${tomlStr(value)}`)
|
|
168
|
-
.join(", ")} }`;
|
|
169
|
-
}
|
|
170
|
-
function codexMcpServerToml(name, server, plan, toolTimeoutMs) {
|
|
171
|
-
const fields = [];
|
|
172
|
-
// Codex takes whole seconds per server; the server is told what was applied.
|
|
173
|
-
const appliedTimeoutMs = toolTimeoutMs === undefined
|
|
174
|
-
? undefined
|
|
175
|
-
: Math.ceil(toolTimeoutMs / 1000) * 1000;
|
|
176
|
-
if (appliedTimeoutMs !== undefined) {
|
|
177
|
-
fields.push(`tool_timeout_sec = ${appliedTimeoutMs / 1000}`);
|
|
178
|
-
}
|
|
179
|
-
if (server.url) {
|
|
180
|
-
fields.push(`url = ${tomlStr(server.url)}`);
|
|
181
|
-
const headers = withAppliedToolTimeout(server.headers, appliedTimeoutMs);
|
|
182
|
-
if (headers && Object.keys(headers).length > 0) {
|
|
183
|
-
const envHeaders = {};
|
|
184
|
-
for (const [header, value] of Object.entries(headers).sort(([left], [right]) => compareStrings(left, right))) {
|
|
185
|
-
const envName = `TANGLE_MCP_HEADER_${sha256(`${name}\0${header}`)
|
|
186
|
-
.slice(0, 24)
|
|
187
|
-
.toUpperCase()}`;
|
|
188
|
-
setPlanEnvironment(plan, envName, value);
|
|
189
|
-
envHeaders[header] = envName;
|
|
190
|
-
}
|
|
191
|
-
fields.push(`env_http_headers = ${tomlInlineStringTable(envHeaders)}`);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
if (!server.command) {
|
|
196
|
-
throw new Error(`Codex MCP server "${name}" must define url or command`);
|
|
197
|
-
}
|
|
198
|
-
fields.push(`command = ${tomlStr(server.command)}`);
|
|
199
|
-
if (server.args?.length) {
|
|
200
|
-
fields.push(concatWorkspacePlanArguments("args = [", joinWorkspacePlanArguments(server.args.map(tomlProfileConfigValue), ", "), "]"));
|
|
201
|
-
}
|
|
202
|
-
if (server.cwd)
|
|
203
|
-
fields.push(`cwd = ${tomlStr(server.cwd)}`);
|
|
204
|
-
if (server.env && Object.keys(server.env).length > 0) {
|
|
205
|
-
const envNames = Object.keys(server.env).sort(compareStrings);
|
|
206
|
-
for (const envName of envNames) {
|
|
207
|
-
setPlanEnvironment(plan, envName, server.env[envName]);
|
|
208
|
-
}
|
|
209
|
-
fields.push(`env_vars = [${envNames.map(tomlStr).join(", ")}]`);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return concatWorkspacePlanArguments("{ ", joinWorkspacePlanArguments(fields, ", "), " }");
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Lower one canonical MCP server to the shape `pi-mcp-adapter` reads from `.pi/mcp.json`.
|
|
216
|
-
*
|
|
217
|
-
* The adapter consumes the same `{mcpServers: {...}}` document Claude and Kimi use, so this
|
|
218
|
-
* mirrors {@link kimiMcpServer} rather than inventing a pi dialect. `directTools` is added by the
|
|
219
|
-
* caller, not here, because it is a policy about tool surfacing rather than a property of the
|
|
220
|
-
* server definition.
|
|
221
|
-
*/
|
|
222
|
-
function piMcpServer(server) {
|
|
223
|
-
return kimiMcpServer(server);
|
|
224
|
-
}
|
|
225
|
-
function kimiMcpServer(server) {
|
|
226
|
-
if (server.url) {
|
|
227
|
-
return {
|
|
228
|
-
url: server.url,
|
|
229
|
-
transport: server.transport ?? "http",
|
|
230
|
-
...(server.headers ? { headers: server.headers } : {}),
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
return {
|
|
234
|
-
command: server.command,
|
|
235
|
-
...(server.args ? { args: server.args } : {}),
|
|
236
|
-
...(server.env ? { env: server.env } : {}),
|
|
237
|
-
...(server.cwd ? { cwd: server.cwd } : {}),
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
import { OPENCODE_CONFIG_FILE, OPENCODE_CONFIG_SCHEMA } from "./harness.js";
|
|
241
|
-
import { openCodeMcpConfig } from "./mcp-opencode.js";
|
|
242
|
-
import { mcpToolTimeouts, withAppliedToolTimeout, } from "./mcp-timeouts.js";
|
|
243
|
-
import { sha256 } from "./plan-digest.js";
|
|
244
|
-
import { concatWorkspacePlanArguments, joinWorkspacePlanArguments, serializeWorkspacePlanJson, tomlProfileConfigValue, tomlStr, } from "./plan-json.js";
|
|
245
|
-
import { setPlanEnvironment } from "./profile-plan.js";
|
|
246
|
-
import { compareStrings } from "./workspace-files.js";
|
package/dist/model-id.d.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The one canonicalization of a model id, shared by everything that writes a
|
|
3
|
-
* model id into an {@link AgentProfile} and everything that later compares one
|
|
4
|
-
* against it.
|
|
5
|
-
*
|
|
6
|
-
* A model id is qualified by prepending provider segments, and the same model
|
|
7
|
-
* is legitimately spelled at more than one qualification level as it travels:
|
|
8
|
-
* the caller sends `{ provider: "openai-compat", model: "deepseek/deepseek-v4-flash" }`,
|
|
9
|
-
* the profile records the qualified `openai-compat/deepseek/deepseek-v4-flash`,
|
|
10
|
-
* and the CLI is spawned with that qualified form. Two spellings, one model.
|
|
11
|
-
*
|
|
12
|
-
* Every writer and every reader must run both sides through here, so a
|
|
13
|
-
* comparison cannot mistake two spellings of one model for a conflict. Two
|
|
14
|
-
* independent copies of this rule are exactly how that bug arises.
|
|
15
|
-
*/
|
|
16
|
-
/**
|
|
17
|
-
* Qualify `modelId` with `provider` — the canonical form of a model id.
|
|
18
|
-
*
|
|
19
|
-
* The already-qualified check is prefix-SPECIFIC rather than "already contains
|
|
20
|
-
* a slash": a router slug legitimately carries an upstream segment of its own
|
|
21
|
-
* (`deepseek/deepseek-v4-flash`, `zai/glm-4.7`), so a generic slash test would
|
|
22
|
-
* leave those unqualified, while an unconditional prefix would double-qualify
|
|
23
|
-
* an id that has already been through here.
|
|
24
|
-
*
|
|
25
|
-
* Idempotent by construction:
|
|
26
|
-
* `qualifyModelId(p, qualifyModelId(p, m)) === qualifyModelId(p, m)`.
|
|
27
|
-
*/
|
|
28
|
-
export declare function qualifyModelId<T extends string | undefined>(provider: string | undefined, modelId: T): T;
|
|
29
|
-
/**
|
|
30
|
-
* Remove one exact `provider/` qualification from `modelId`.
|
|
31
|
-
*
|
|
32
|
-
* This is the inverse operation for a model id that was qualified by this
|
|
33
|
-
* provider. It removes only the known outer prefix and preserves upstream
|
|
34
|
-
* segments such as `zai/glm-4.7`.
|
|
35
|
-
*
|
|
36
|
-
* The helper removes one outer qualification per call. It leaves missing
|
|
37
|
-
* providers and model ids unchanged.
|
|
38
|
-
*/
|
|
39
|
-
export declare function unqualifyModelId(provider: string | undefined, modelId: string): string;
|
|
40
|
-
export declare function unqualifyModelId(provider: string | undefined, modelId: undefined): undefined;
|
|
41
|
-
export declare function unqualifyModelId(provider: string | undefined, modelId: string | undefined): string | undefined;
|
|
42
|
-
/**
|
|
43
|
-
* Do two model ids name the same model?
|
|
44
|
-
*
|
|
45
|
-
* Both operands are qualified by `provider` before comparison, so the
|
|
46
|
-
* qualification level a caller happened to use is not a difference. Two
|
|
47
|
-
* different model names are still different — this narrows a comparison to the
|
|
48
|
-
* model's identity, it does not make the comparison permissive.
|
|
49
|
-
*
|
|
50
|
-
* `provider` is optional because a profile may carry a `model.default` that was
|
|
51
|
-
* qualified by a writer which did not also record `model.provider`. With no
|
|
52
|
-
* provider to qualify by, a bare id and its single-segment-qualified spelling
|
|
53
|
-
* are still the same model, which is the only tolerance the undeclared case gets.
|
|
54
|
-
*/
|
|
55
|
-
export declare function modelIdsMatch(a: string | undefined, b: string | undefined, provider?: string): boolean;
|
package/dist/model-id.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The one canonicalization of a model id, shared by everything that writes a
|
|
3
|
-
* model id into an {@link AgentProfile} and everything that later compares one
|
|
4
|
-
* against it.
|
|
5
|
-
*
|
|
6
|
-
* A model id is qualified by prepending provider segments, and the same model
|
|
7
|
-
* is legitimately spelled at more than one qualification level as it travels:
|
|
8
|
-
* the caller sends `{ provider: "openai-compat", model: "deepseek/deepseek-v4-flash" }`,
|
|
9
|
-
* the profile records the qualified `openai-compat/deepseek/deepseek-v4-flash`,
|
|
10
|
-
* and the CLI is spawned with that qualified form. Two spellings, one model.
|
|
11
|
-
*
|
|
12
|
-
* Every writer and every reader must run both sides through here, so a
|
|
13
|
-
* comparison cannot mistake two spellings of one model for a conflict. Two
|
|
14
|
-
* independent copies of this rule are exactly how that bug arises.
|
|
15
|
-
*/
|
|
16
|
-
/**
|
|
17
|
-
* Qualify `modelId` with `provider` — the canonical form of a model id.
|
|
18
|
-
*
|
|
19
|
-
* The already-qualified check is prefix-SPECIFIC rather than "already contains
|
|
20
|
-
* a slash": a router slug legitimately carries an upstream segment of its own
|
|
21
|
-
* (`deepseek/deepseek-v4-flash`, `zai/glm-4.7`), so a generic slash test would
|
|
22
|
-
* leave those unqualified, while an unconditional prefix would double-qualify
|
|
23
|
-
* an id that has already been through here.
|
|
24
|
-
*
|
|
25
|
-
* Idempotent by construction:
|
|
26
|
-
* `qualifyModelId(p, qualifyModelId(p, m)) === qualifyModelId(p, m)`.
|
|
27
|
-
*/
|
|
28
|
-
export function qualifyModelId(provider, modelId) {
|
|
29
|
-
if (!provider || !modelId)
|
|
30
|
-
return modelId;
|
|
31
|
-
if (modelId === provider || modelId.startsWith(`${provider}/`))
|
|
32
|
-
return modelId;
|
|
33
|
-
return `${provider}/${modelId}`;
|
|
34
|
-
}
|
|
35
|
-
export function unqualifyModelId(provider, modelId) {
|
|
36
|
-
if (!provider || !modelId)
|
|
37
|
-
return modelId;
|
|
38
|
-
const providerPrefix = `${provider}/`;
|
|
39
|
-
return modelId.startsWith(providerPrefix)
|
|
40
|
-
? modelId.slice(providerPrefix.length)
|
|
41
|
-
: modelId;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Do two model ids name the same model?
|
|
45
|
-
*
|
|
46
|
-
* Both operands are qualified by `provider` before comparison, so the
|
|
47
|
-
* qualification level a caller happened to use is not a difference. Two
|
|
48
|
-
* different model names are still different — this narrows a comparison to the
|
|
49
|
-
* model's identity, it does not make the comparison permissive.
|
|
50
|
-
*
|
|
51
|
-
* `provider` is optional because a profile may carry a `model.default` that was
|
|
52
|
-
* qualified by a writer which did not also record `model.provider`. With no
|
|
53
|
-
* provider to qualify by, a bare id and its single-segment-qualified spelling
|
|
54
|
-
* are still the same model, which is the only tolerance the undeclared case gets.
|
|
55
|
-
*/
|
|
56
|
-
export function modelIdsMatch(a, b, provider) {
|
|
57
|
-
if (a === b)
|
|
58
|
-
return true;
|
|
59
|
-
if (a === undefined || b === undefined)
|
|
60
|
-
return false;
|
|
61
|
-
if (qualifyModelId(provider, a) === qualifyModelId(provider, b))
|
|
62
|
-
return true;
|
|
63
|
-
if (provider)
|
|
64
|
-
return false;
|
|
65
|
-
return ((!a.includes("/") && b.endsWith(`/${a}`)) ||
|
|
66
|
-
(!b.includes("/") && a.endsWith(`/${b}`)));
|
|
67
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
export interface PiModelsConfigInput {
|
|
2
|
-
/** Provider key in models.json and the matching `--provider` value. */
|
|
3
|
-
providerName: string;
|
|
4
|
-
/** Model id in models.json and the matching `--model` value. */
|
|
5
|
-
modelId: string;
|
|
6
|
-
/** Provider endpoint. OpenAI-compatible endpoints include `/v1`. */
|
|
7
|
-
baseUrl: string;
|
|
8
|
-
/** Literal in-box credential or an environment reference. */
|
|
9
|
-
apiKey: string;
|
|
10
|
-
/** Pi wire protocol. */
|
|
11
|
-
api?: "anthropic-messages" | "google-generative-ai" | "openai-completions" | "openai-responses";
|
|
12
|
-
contextWindow?: number;
|
|
13
|
-
maxTokens?: number;
|
|
14
|
-
reasoning?: boolean;
|
|
15
|
-
/** OpenAI-compatible completions may reject the optional store field. */
|
|
16
|
-
supportsStore?: boolean;
|
|
17
|
-
}
|
|
18
|
-
export interface PiModelsConfigFile {
|
|
19
|
-
path: string;
|
|
20
|
-
content: string;
|
|
21
|
-
mode: number;
|
|
22
|
-
}
|
|
23
|
-
export declare const PI_MODELS_CONFIG_PATH = ".pi/agent/models.json";
|
|
24
|
-
export interface PiRuntimePaths {
|
|
25
|
-
agentDir: string;
|
|
26
|
-
sessionDir: string;
|
|
27
|
-
}
|
|
28
|
-
/** Resolve Pi's private roots from the one runtime HOME supplied to Pi. */
|
|
29
|
-
export declare function resolvePiRuntimePaths(runtimeHome: string): PiRuntimePaths;
|
|
30
|
-
export declare function buildPiModelsConfig(input: PiModelsConfigInput): Record<string, unknown>;
|
|
31
|
-
/** Build the owner-only Pi file used by headless and interactive processes. */
|
|
32
|
-
export declare function buildPiModelsConfigFile(input: PiModelsConfigInput): PiModelsConfigFile;
|
package/dist/pi-models-config.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* One custom-model shape for upstream Pi and the Prime fork.
|
|
3
|
-
*
|
|
4
|
-
* Both agents read the same `models.json` provider schema.
|
|
5
|
-
* Callers own the file lifecycle, but they must not build different payloads.
|
|
6
|
-
*/
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
export const PI_MODELS_CONFIG_PATH = ".pi/agent/models.json";
|
|
9
|
-
/** Resolve Pi's private roots from the one runtime HOME supplied to Pi. */
|
|
10
|
-
export function resolvePiRuntimePaths(runtimeHome) {
|
|
11
|
-
const home = path.resolve(runtimeHome);
|
|
12
|
-
const agentDir = path.dirname(path.join(home, PI_MODELS_CONFIG_PATH));
|
|
13
|
-
return {
|
|
14
|
-
agentDir,
|
|
15
|
-
sessionDir: path.join(agentDir, "sessions"),
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
const DEFAULT_CONTEXT_WINDOW = 128_000;
|
|
19
|
-
export function buildPiModelsConfig(input) {
|
|
20
|
-
for (const [field, value] of [
|
|
21
|
-
["providerName", input.providerName],
|
|
22
|
-
["modelId", input.modelId],
|
|
23
|
-
["baseUrl", input.baseUrl],
|
|
24
|
-
["apiKey", input.apiKey],
|
|
25
|
-
]) {
|
|
26
|
-
if (typeof value !== "string" || value.trim().length === 0) {
|
|
27
|
-
throw new Error(`pi models.json requires a non-empty "${field}"; ` +
|
|
28
|
-
"the caller must supply provider, model, baseUrl, and apiKey");
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return {
|
|
32
|
-
providers: {
|
|
33
|
-
[input.providerName]: {
|
|
34
|
-
name: input.providerName,
|
|
35
|
-
baseUrl: input.baseUrl,
|
|
36
|
-
api: input.api ?? "openai-completions",
|
|
37
|
-
apiKey: input.apiKey,
|
|
38
|
-
models: [
|
|
39
|
-
{
|
|
40
|
-
id: input.modelId,
|
|
41
|
-
name: input.modelId,
|
|
42
|
-
reasoning: input.reasoning ?? false,
|
|
43
|
-
...(input.supportsStore !== undefined
|
|
44
|
-
? { compat: { supportsStore: input.supportsStore } }
|
|
45
|
-
: {}),
|
|
46
|
-
input: ["text", "image"],
|
|
47
|
-
contextWindow: input.contextWindow ?? DEFAULT_CONTEXT_WINDOW,
|
|
48
|
-
...(input.maxTokens !== undefined
|
|
49
|
-
? { maxTokens: input.maxTokens }
|
|
50
|
-
: {}),
|
|
51
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
52
|
-
},
|
|
53
|
-
],
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
/** Build the owner-only Pi file used by headless and interactive processes. */
|
|
59
|
-
export function buildPiModelsConfigFile(input) {
|
|
60
|
-
return {
|
|
61
|
-
path: PI_MODELS_CONFIG_PATH,
|
|
62
|
-
content: `${JSON.stringify(buildPiModelsConfig(input), null, 2)}\n`,
|
|
63
|
-
mode: 0o600,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export declare function applyWorkspacePlan(plan: WorkspacePlan, workspaceDir: string, options?: ApplyWorkspacePlanOptions): WorkspacePlanReceipt;
|
|
2
|
-
/**
|
|
3
|
-
* Resolve opaque profile references through an executor-owned provider, then
|
|
4
|
-
* apply the resulting private native files and launch inputs. Resolution
|
|
5
|
-
* completes before any workspace write, and resolved values never enter the
|
|
6
|
-
* public plan, digest, receipt, logs, or error messages.
|
|
7
|
-
*/
|
|
8
|
-
export declare function applyWorkspacePlanWithSecretProvider(plan: WorkspacePlan, workspaceDir: string, secretProvider: AgentProfileSecretProvider, options?: ApplyWorkspacePlanOptions): Promise<WorkspacePlanApplication>;
|
|
9
|
-
import type { AgentProfileSecretProvider } from "@tangle-network/agent-interface";
|
|
10
|
-
import type { ApplyWorkspacePlanOptions, WorkspacePlan, WorkspacePlanApplication, WorkspacePlanReceipt } from "./plan-types.js";
|
package/dist/plan-application.js
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
export function applyWorkspacePlan(plan, workspaceDir, options = {}) {
|
|
2
|
-
return applyResolvedWorkspacePlan(plan, resolvePublicWorkspacePlan(plan), workspaceDir, options).receipt;
|
|
3
|
-
}
|
|
4
|
-
/**
|
|
5
|
-
* Resolve opaque profile references through an executor-owned provider, then
|
|
6
|
-
* apply the resulting private native files and launch inputs. Resolution
|
|
7
|
-
* completes before any workspace write, and resolved values never enter the
|
|
8
|
-
* public plan, digest, receipt, logs, or error messages.
|
|
9
|
-
*/
|
|
10
|
-
export async function applyWorkspacePlanWithSecretProvider(plan, workspaceDir, secretProvider, options = {}) {
|
|
11
|
-
const planDigest = hashWorkspacePlan(plan);
|
|
12
|
-
const resolved = await resolvePrivateWorkspacePlan(plan, secretProvider, options.secretFileSlotReferences);
|
|
13
|
-
return applyResolvedWorkspacePlan(plan, resolved, workspaceDir, options, planDigest);
|
|
14
|
-
}
|
|
15
|
-
function secretRefLabel(ref) {
|
|
16
|
-
return JSON.stringify(ref.key);
|
|
17
|
-
}
|
|
18
|
-
function requireResolvedSecretValue(ref, value) {
|
|
19
|
-
if (typeof value !== "string" || value.trim().length === 0) {
|
|
20
|
-
throw new Error(`AgentProfile secret provider returned no value for ${secretRefLabel(ref)}`);
|
|
21
|
-
}
|
|
22
|
-
return value;
|
|
23
|
-
}
|
|
24
|
-
function setValueAtJsonPath(root, path, value) {
|
|
25
|
-
let current = root;
|
|
26
|
-
for (const segment of path.slice(0, -1)) {
|
|
27
|
-
if (current === null ||
|
|
28
|
-
typeof current !== "object" ||
|
|
29
|
-
!Object.hasOwn(current, segment)) {
|
|
30
|
-
throw new Error("Generated profile JSON secret path no longer exists");
|
|
31
|
-
}
|
|
32
|
-
current = current[segment];
|
|
33
|
-
}
|
|
34
|
-
const finalSegment = path.at(-1);
|
|
35
|
-
if (finalSegment === undefined ||
|
|
36
|
-
current === null ||
|
|
37
|
-
typeof current !== "object" ||
|
|
38
|
-
!Object.hasOwn(current, finalSegment)) {
|
|
39
|
-
throw new Error("Generated profile JSON secret path no longer exists");
|
|
40
|
-
}
|
|
41
|
-
Object.defineProperty(current, finalSegment, {
|
|
42
|
-
value,
|
|
43
|
-
enumerable: true,
|
|
44
|
-
configurable: true,
|
|
45
|
-
writable: true,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
function resolvePublicWorkspacePlan(plan) {
|
|
49
|
-
const files = normalizePlanFiles(plan.files);
|
|
50
|
-
for (const file of files) {
|
|
51
|
-
const ref = file.secretSlots[0]?.ref;
|
|
52
|
-
if (ref) {
|
|
53
|
-
throw new Error(`Workspace plan requires an AgentProfileSecretProvider for ${secretRefLabel(ref)}`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
const env = Object.fromEntries(Object.entries(plan.env).map(([name, value]) => {
|
|
57
|
-
if (typeof value !== "string") {
|
|
58
|
-
throw new Error(`Workspace plan requires an AgentProfileSecretProvider for ${secretRefLabel(value)}`);
|
|
59
|
-
}
|
|
60
|
-
return [name, value];
|
|
61
|
-
}));
|
|
62
|
-
const flags = plan.flags.map((argument) => {
|
|
63
|
-
const normalized = normalizeWorkspacePlanArgument(argument);
|
|
64
|
-
if (typeof normalized !== "string") {
|
|
65
|
-
const segment = normalized.segments.find((value) => typeof value !== "string");
|
|
66
|
-
if (!segment) {
|
|
67
|
-
throw new Error("Workspace plan argument template is malformed");
|
|
68
|
-
}
|
|
69
|
-
throw new Error(`Workspace plan requires an AgentProfileSecretProvider for ${secretRefLabel(segment.ref)}`);
|
|
70
|
-
}
|
|
71
|
-
return normalized;
|
|
72
|
-
});
|
|
73
|
-
return {
|
|
74
|
-
files,
|
|
75
|
-
env,
|
|
76
|
-
flags,
|
|
77
|
-
...promptIntentFields(plan, samePromptIntentValue),
|
|
78
|
-
...systemPromptBindingField(plan),
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* OpenCode substitutes `{env:NAME}` textually into its config before parsing
|
|
83
|
-
* it, so only a value that cannot break out of a JSON string or start another
|
|
84
|
-
* substitution may stand behind a reference.
|
|
85
|
-
*/
|
|
86
|
-
const SUBSTITUTABLE_SECRET_VALUE = /^[\x20-\x7e]+$/;
|
|
87
|
-
const UNSUBSTITUTABLE_SECRET_CHARACTERS = /["\\{}]/;
|
|
88
|
-
function secretSlotEnvironmentName(ref) {
|
|
89
|
-
const digest = createHash("sha256")
|
|
90
|
-
.update(ref.key, "utf8")
|
|
91
|
-
.digest("hex")
|
|
92
|
-
.slice(0, 24)
|
|
93
|
-
.toUpperCase();
|
|
94
|
-
return `TANGLE_PROFILE_SECRET_${digest}`;
|
|
95
|
-
}
|
|
96
|
-
async function resolvePrivateWorkspacePlan(plan, secretProvider, fileSlotReferences) {
|
|
97
|
-
if (!secretProvider || typeof secretProvider.get !== "function") {
|
|
98
|
-
throw new Error("AgentProfileSecretProvider.get is required");
|
|
99
|
-
}
|
|
100
|
-
const pending = new Map();
|
|
101
|
-
const resolveRawRef = (ref) => {
|
|
102
|
-
let raw = pending.get(ref.key);
|
|
103
|
-
if (!raw) {
|
|
104
|
-
raw = (async () => {
|
|
105
|
-
let value;
|
|
106
|
-
try {
|
|
107
|
-
value = await secretProvider.get(ref.key);
|
|
108
|
-
}
|
|
109
|
-
catch (cause) {
|
|
110
|
-
// The executor's refusal (a key it will not serve) rides along as
|
|
111
|
-
// the cause; a provider error carries no resolved value.
|
|
112
|
-
throw new Error(`AgentProfile secret provider failed for ${secretRefLabel(ref)}`, { cause });
|
|
113
|
-
}
|
|
114
|
-
return requireResolvedSecretValue(ref, value);
|
|
115
|
-
})();
|
|
116
|
-
pending.set(ref.key, raw);
|
|
117
|
-
}
|
|
118
|
-
return raw;
|
|
119
|
-
};
|
|
120
|
-
const resolveRef = async (input) => {
|
|
121
|
-
const ref = agentProfileSecretRefSchema.parse(input);
|
|
122
|
-
const value = await resolveRawRef(ref);
|
|
123
|
-
return ref.format === "bearer" ? `Bearer ${value}` : value;
|
|
124
|
-
};
|
|
125
|
-
const resolveFileSlot = async (input) => {
|
|
126
|
-
const ref = agentProfileSecretRefSchema.parse(input);
|
|
127
|
-
const value = await resolveRawRef(ref);
|
|
128
|
-
if (!fileSlotReferences) {
|
|
129
|
-
return ref.format === "bearer" ? `Bearer ${value}` : value;
|
|
130
|
-
}
|
|
131
|
-
// Never write the value in place: the file is readable by the process
|
|
132
|
-
// that loads it, and so by every tool that process runs.
|
|
133
|
-
if (!SUBSTITUTABLE_SECRET_VALUE.test(value) ||
|
|
134
|
-
UNSUBSTITUTABLE_SECRET_CHARACTERS.test(value)) {
|
|
135
|
-
throw new Error(`AgentProfile secret ${secretRefLabel(ref)} has a value that cannot be referenced from a secret OpenCode config: it may hold only printable ASCII without quotes, backslashes or braces`);
|
|
136
|
-
}
|
|
137
|
-
const name = secretSlotEnvironmentName(ref);
|
|
138
|
-
fileSlotReferences[name] = value;
|
|
139
|
-
return ref.format === "bearer" ? `Bearer {env:${name}}` : `{env:${name}}`;
|
|
140
|
-
};
|
|
141
|
-
const files = await Promise.all(normalizePlanFiles(plan.files).map(async (file) => {
|
|
142
|
-
if (file.secretSlots.length === 0)
|
|
143
|
-
return file;
|
|
144
|
-
// Generated native config files with secret slots are JSON objects.
|
|
145
|
-
const parsed = JSON.parse(file.content);
|
|
146
|
-
const values = await Promise.all(file.secretSlots.map(async (slot) => ({
|
|
147
|
-
path: slot.path,
|
|
148
|
-
value: await resolveFileSlot(slot.ref),
|
|
149
|
-
})));
|
|
150
|
-
for (const resolved of values) {
|
|
151
|
-
setValueAtJsonPath(parsed, resolved.path, resolved.value);
|
|
152
|
-
}
|
|
153
|
-
return {
|
|
154
|
-
...file,
|
|
155
|
-
content: JSON.stringify(parsed, null, 2),
|
|
156
|
-
secretSlots: [],
|
|
157
|
-
};
|
|
158
|
-
}));
|
|
159
|
-
const env = Object.fromEntries(await Promise.all(Object.entries(plan.env).map(async ([name, value]) => [
|
|
160
|
-
name,
|
|
161
|
-
typeof value === "string" ? value : await resolveRef(value),
|
|
162
|
-
])));
|
|
163
|
-
const flags = await Promise.all(plan.flags.map(async (argument) => {
|
|
164
|
-
const normalized = normalizeWorkspacePlanArgument(argument);
|
|
165
|
-
if (typeof normalized === "string")
|
|
166
|
-
return normalized;
|
|
167
|
-
const segments = await Promise.all(normalized.segments.map(async (segment) => {
|
|
168
|
-
if (typeof segment === "string")
|
|
169
|
-
return segment;
|
|
170
|
-
const value = await resolveRef(segment.ref);
|
|
171
|
-
return segment.encoding === "json-string"
|
|
172
|
-
? JSON.stringify(value)
|
|
173
|
-
: value;
|
|
174
|
-
}));
|
|
175
|
-
return segments.join("");
|
|
176
|
-
}));
|
|
177
|
-
return {
|
|
178
|
-
files,
|
|
179
|
-
env,
|
|
180
|
-
flags,
|
|
181
|
-
...promptIntentFields(plan, samePromptIntentValue),
|
|
182
|
-
...systemPromptBindingField(plan),
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
import { createHash } from "node:crypto";
|
|
186
|
-
import { agentProfileSecretRefSchema } from "@tangle-network/agent-interface";
|
|
187
|
-
import { hashWorkspacePlan } from "./plan-digest.js";
|
|
188
|
-
import { normalizePlanFiles, normalizeWorkspacePlanArgument, } from "./plan-normalize.js";
|
|
189
|
-
import { promptIntentFields, samePromptIntentValue, systemPromptBindingField, } from "./plan-prompt.js";
|
|
190
|
-
import { applyResolvedWorkspacePlan, } from "./workspace-apply.js";
|