@tangle-network/agent-profile-materialize 1.1.6 → 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.
Files changed (95) hide show
  1. package/dist/index.d.ts +1226 -29
  2. package/dist/index.js +4987 -23
  3. package/package.json +7 -5
  4. package/dist/candidate-types.d.ts +0 -36
  5. package/dist/candidate-types.js +0 -1
  6. package/dist/candidate.d.ts +0 -9
  7. package/dist/candidate.js +0 -283
  8. package/dist/commands.d.ts +0 -2
  9. package/dist/commands.js +0 -32
  10. package/dist/control-maps.d.ts +0 -58
  11. package/dist/control-maps.js +0 -277
  12. package/dist/execution-controls.d.ts +0 -5
  13. package/dist/execution-controls.js +0 -199
  14. package/dist/harness-prompt.d.ts +0 -22
  15. package/dist/harness-prompt.js +0 -64
  16. package/dist/harness.d.ts +0 -131
  17. package/dist/harness.js +0 -416
  18. package/dist/hooks.d.ts +0 -6
  19. package/dist/hooks.js +0 -250
  20. package/dist/managed-workspace-bind.d.ts +0 -4
  21. package/dist/managed-workspace-bind.js +0 -112
  22. package/dist/managed-workspace-stage.d.ts +0 -25
  23. package/dist/managed-workspace-stage.js +0 -199
  24. package/dist/managed-workspace-types.d.ts +0 -38
  25. package/dist/managed-workspace-types.js +0 -1
  26. package/dist/managed-workspace.d.ts +0 -37
  27. package/dist/managed-workspace.js +0 -290
  28. package/dist/mcp-config-ownership.d.ts +0 -13
  29. package/dist/mcp-config-ownership.js +0 -79
  30. package/dist/mcp-opencode.d.ts +0 -25
  31. package/dist/mcp-opencode.js +0 -52
  32. package/dist/mcp-timeouts.d.ts +0 -7
  33. package/dist/mcp-timeouts.js +0 -48
  34. package/dist/mcp.d.ts +0 -4
  35. package/dist/mcp.js +0 -246
  36. package/dist/model-id.d.ts +0 -55
  37. package/dist/model-id.js +0 -67
  38. package/dist/pi-models-config.d.ts +0 -32
  39. package/dist/pi-models-config.js +0 -65
  40. package/dist/plan-application.d.ts +0 -10
  41. package/dist/plan-application.js +0 -190
  42. package/dist/plan-digest.d.ts +0 -18
  43. package/dist/plan-digest.js +0 -116
  44. package/dist/plan-json.d.ts +0 -13
  45. package/dist/plan-json.js +0 -91
  46. package/dist/plan-normalize.d.ts +0 -12
  47. package/dist/plan-normalize.js +0 -168
  48. package/dist/plan-prompt.d.ts +0 -38
  49. package/dist/plan-prompt.js +0 -49
  50. package/dist/plan-support.d.ts +0 -9
  51. package/dist/plan-support.js +0 -22
  52. package/dist/plan-types.d.ts +0 -341
  53. package/dist/plan-types.js +0 -105
  54. package/dist/prime-agent-dir.d.ts +0 -64
  55. package/dist/prime-agent-dir.js +0 -173
  56. package/dist/prime-fork.d.ts +0 -155
  57. package/dist/prime-fork.js +0 -262
  58. package/dist/prime-profile.d.ts +0 -88
  59. package/dist/prime-profile.js +0 -297
  60. package/dist/profile-bundle-cache.d.ts +0 -37
  61. package/dist/profile-bundle-cache.js +0 -320
  62. package/dist/profile-bundle-key.d.ts +0 -69
  63. package/dist/profile-bundle-key.js +0 -62
  64. package/dist/profile-bundle-lru.d.ts +0 -56
  65. package/dist/profile-bundle-lru.js +0 -220
  66. package/dist/profile-materializer.d.ts +0 -24
  67. package/dist/profile-materializer.js +0 -114
  68. package/dist/profile-plan.d.ts +0 -16
  69. package/dist/profile-plan.js +0 -90
  70. package/dist/profile-prompt.d.ts +0 -4
  71. package/dist/profile-prompt.js +0 -107
  72. package/dist/profile-support.d.ts +0 -8
  73. package/dist/profile-support.js +0 -6
  74. package/dist/resource-lowering.d.ts +0 -6
  75. package/dist/resource-lowering.js +0 -103
  76. package/dist/resource-resolution.d.ts +0 -27
  77. package/dist/resource-resolution.js +0 -265
  78. package/dist/secret-concurrent-revision.d.ts +0 -30
  79. package/dist/secret-concurrent-revision.js +0 -291
  80. package/dist/secret-session-view.d.ts +0 -36
  81. package/dist/secret-session-view.js +0 -179
  82. package/dist/subagents.d.ts +0 -6
  83. package/dist/subagents.js +0 -137
  84. package/dist/tool-bin-path.d.ts +0 -23
  85. package/dist/tool-bin-path.js +0 -61
  86. package/dist/workspace-apply-files.d.ts +0 -57
  87. package/dist/workspace-apply-files.js +0 -193
  88. package/dist/workspace-apply-publish.d.ts +0 -22
  89. package/dist/workspace-apply-publish.js +0 -178
  90. package/dist/workspace-apply.d.ts +0 -11
  91. package/dist/workspace-apply.js +0 -124
  92. package/dist/workspace-files.d.ts +0 -8
  93. package/dist/workspace-files.js +0 -46
  94. package/dist/workspace-launch.d.ts +0 -3
  95. 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";
@@ -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;
@@ -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";
@@ -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";