@skein-code/cli 0.3.28 → 0.3.30
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/README.md +79 -27
- package/dist/cli.js +1062 -268
- package/dist/cli.js.map +1 -1
- package/docs/MULTI_MODEL_TEAMS.md +93 -19
- package/docs/NEXT_STEPS.md +53 -26
- package/package.json +14 -1
package/dist/cli.js
CHANGED
|
@@ -220,7 +220,7 @@ function isSqliteBusy(error) {
|
|
|
220
220
|
// package.json
|
|
221
221
|
var package_default = {
|
|
222
222
|
name: "@skein-code/cli",
|
|
223
|
-
version: "0.3.
|
|
223
|
+
version: "0.3.30",
|
|
224
224
|
description: "A context-first, model-agnostic coding agent with an auditable terminal workspace.",
|
|
225
225
|
type: "module",
|
|
226
226
|
license: "MIT",
|
|
@@ -236,6 +236,17 @@ var package_default = {
|
|
|
236
236
|
},
|
|
237
237
|
skein: {
|
|
238
238
|
releaseNotes: [
|
|
239
|
+
"Named primary connections now target third-party compatible relays only, use explicit env or none authentication, and resolve one complete profile automatically while requiring an explicit choice for ambiguity",
|
|
240
|
+
"OpenAI Responses is the recommended new transport with POST /responses, store false, typed SSE, normalized tools and usage, and bounded exact output-item replay for stateless reasoning continuation",
|
|
241
|
+
"OpenAI Chat Completions and Anthropic Messages remain explicit relay transports without cross-protocol retries; inference and OpenAI-style model catalog base URLs are configured independently",
|
|
242
|
+
"Relay model discovery now uses a bounded 15-minute ETag cache isolated by endpoint and credential fingerprints, invalidates authentication failures, and never stores or echoes credential values",
|
|
243
|
+
"First-run and agents setup flows are relay-only, save only credential environment-variable names, and expose redacted connection state consistently across TUI, text, JSON, JSONL, doctor, and config output",
|
|
244
|
+
"Provider diagnostics redact secrets, authorization headers, URL credentials, and query strings while Responses replay state is capped at 128 items and 4 MiB in memory and persisted sessions",
|
|
245
|
+
"External delegated CLIs now receive a minimal runtime-owned environment instead of inheriting provider, Skein, relay, or NODE_OPTIONS secrets",
|
|
246
|
+
"TERM=dumb and screen-reader sessions now use deterministic ASCII, monochrome, reduced-motion, non-incremental rendering without losing keyboard input",
|
|
247
|
+
"SKEIN_SCREEN_READER=1 enables Ink accessibility output with semantic timeline, permission, and composer roles plus clean phase boundaries",
|
|
248
|
+
"PTY release gates now replay eight terminal scenarios through a headless emulator and reject final-frame overflow, stale panels, announcement joins, and missing status",
|
|
249
|
+
"A local terminal UI benchmark enforces 25 ms input and 150 ms streaming-render p95 budgets while retaining the final streamed chunk",
|
|
239
250
|
"MCP trust and revoke confirmation rendering now has a deterministic cross-platform release regression gate",
|
|
240
251
|
"MCP now follows a no-network search and inspect review before explicit workspace-bound manifest trust and lazy activation",
|
|
241
252
|
"Declarative capability manifests expose redacted source, version, tools, permissions, network, command, path, sensitive-field, process, and completion-evidence effects",
|
|
@@ -319,6 +330,7 @@ var package_default = {
|
|
|
319
330
|
"benchmark:context": "tsx scripts/benchmark-local-index.ts",
|
|
320
331
|
"benchmark:duplication": "tsx scripts/benchmark-duplication.ts",
|
|
321
332
|
"benchmark:token-economy": "tsx scripts/benchmark-token-economy.ts",
|
|
333
|
+
"benchmark:terminal-ui": "tsx scripts/benchmark-terminal-ui.ts",
|
|
322
334
|
"verify:package": "node scripts/verify-package.mjs",
|
|
323
335
|
"release:verify": "npm run check && npm run verify:package --",
|
|
324
336
|
typecheck: "tsc --noEmit",
|
|
@@ -346,6 +358,7 @@ var package_default = {
|
|
|
346
358
|
"@types/diff": "^8.0.0",
|
|
347
359
|
"@types/node": "^22.19.7",
|
|
348
360
|
"@types/react": "^19.2.7",
|
|
361
|
+
"@xterm/headless": "^6.0.0",
|
|
349
362
|
tsup: "^8.5.1",
|
|
350
363
|
tsx: "^4.21.0",
|
|
351
364
|
vitest: "^4.0.18"
|
|
@@ -1917,13 +1930,28 @@ var memoryConfigSchema = z2.object({
|
|
|
1917
1930
|
maxPromptTokens: z2.number().int().positive().max(2e4).optional()
|
|
1918
1931
|
}).partial();
|
|
1919
1932
|
var agentConnectionNameSchema = z2.string().regex(/^[a-z][a-z0-9_-]{0,63}$/);
|
|
1933
|
+
var connectionAuthSchema = z2.discriminatedUnion("type", [
|
|
1934
|
+
z2.object({ type: z2.literal("env"), name: z2.string().regex(/^[A-Z][A-Z0-9_]{0,127}$/) }).strict(),
|
|
1935
|
+
z2.object({ type: z2.literal("none") }).strict()
|
|
1936
|
+
]);
|
|
1937
|
+
var connectionUrlSchema = z2.string().url().refine((value) => {
|
|
1938
|
+
const url = new URL(value);
|
|
1939
|
+
return /^https?:$/i.test(url.protocol) && !url.username && !url.password && !url.search && !url.hash;
|
|
1940
|
+
}, {
|
|
1941
|
+
message: "connection URL must use http or https without credentials, query parameters, or fragments"
|
|
1942
|
+
});
|
|
1920
1943
|
var agentConnectionSchema = z2.object({
|
|
1921
1944
|
provider: z2.enum(["openai", "anthropic", "gemini", "compatible"]),
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1945
|
+
label: z2.string().min(1).max(128).optional(),
|
|
1946
|
+
protocol: z2.enum(["openai-responses", "openai-chat", "anthropic-messages", "gemini"]).optional(),
|
|
1947
|
+
baseUrl: connectionUrlSchema.optional(),
|
|
1948
|
+
modelsBaseUrl: connectionUrlSchema.optional(),
|
|
1949
|
+
defaultModel: z2.string().min(1).max(256).optional(),
|
|
1950
|
+
auth: connectionAuthSchema.optional(),
|
|
1925
1951
|
apiKeyEnv: z2.string().regex(/^[A-Z][A-Z0-9_]{0,127}$/).optional()
|
|
1926
|
-
}).strict()
|
|
1952
|
+
}).strict().refine((value) => !(value.auth && value.apiKeyEnv), {
|
|
1953
|
+
message: "agent connection auth and apiKeyEnv are mutually exclusive"
|
|
1954
|
+
});
|
|
1927
1955
|
var agentTeamConfigSchema = z2.object({
|
|
1928
1956
|
enabled: z2.boolean().optional(),
|
|
1929
1957
|
maxConcurrent: z2.number().int().positive().max(16).optional(),
|
|
@@ -2347,6 +2375,17 @@ function validateAgentConnections(agents) {
|
|
|
2347
2375
|
if (agents?.defaultConnection && !agents.connections?.[agents.defaultConnection]) {
|
|
2348
2376
|
throw new Error(`Agent defaults reference unknown connection ${agents.defaultConnection}.`);
|
|
2349
2377
|
}
|
|
2378
|
+
for (const [name, connection] of Object.entries(agents?.connections ?? {})) {
|
|
2379
|
+
if (connection.protocol && connection.provider !== "compatible") {
|
|
2380
|
+
throw new Error(`Agent connection ${name} must use provider compatible when protocol is explicit.`);
|
|
2381
|
+
}
|
|
2382
|
+
if (connection.provider === "compatible" && connection.protocol === "gemini") {
|
|
2383
|
+
throw new Error(`Agent connection ${name} cannot use the Gemini transport.`);
|
|
2384
|
+
}
|
|
2385
|
+
if (connection.protocol === "anthropic-messages" && !connection.modelsBaseUrl) {
|
|
2386
|
+
throw new Error(`Agent connection ${name} requires modelsBaseUrl for Anthropic transport.`);
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2350
2389
|
for (const [profile, route] of Object.entries(agents?.routes ?? {})) {
|
|
2351
2390
|
if (route.connection && !agents?.connections?.[route.connection]) {
|
|
2352
2391
|
throw new Error(`Agent route ${profile} references unknown connection ${route.connection}.`);
|
|
@@ -2490,6 +2529,8 @@ function configSummary(config) {
|
|
|
2490
2529
|
model: `${config.model.provider}/${config.model.model}`,
|
|
2491
2530
|
endpoint: redactEndpoint(config.model.baseUrl),
|
|
2492
2531
|
apiKey: config.model.apiKey ? "configured" : "missing",
|
|
2532
|
+
activeConnection: config.activeConnection,
|
|
2533
|
+
connectionCatalog: config.connectionCatalog,
|
|
2493
2534
|
context: {
|
|
2494
2535
|
maxTokens: config.context.maxTokens,
|
|
2495
2536
|
topK: config.context.topK
|
|
@@ -2531,8 +2572,11 @@ function configSummary(config) {
|
|
|
2531
2572
|
maxWriterPatchBytes: config.agents.maxWriterPatchBytes,
|
|
2532
2573
|
connections: Object.fromEntries(Object.entries(config.agents.connections ?? {}).map(([name, connection]) => [name, {
|
|
2533
2574
|
provider: connection.provider,
|
|
2575
|
+
protocol: connection.protocol,
|
|
2576
|
+
defaultModel: connection.defaultModel,
|
|
2534
2577
|
endpoint: redactEndpoint(connection.baseUrl),
|
|
2535
|
-
|
|
2578
|
+
modelsEndpoint: redactEndpoint(connection.modelsBaseUrl ?? connection.baseUrl),
|
|
2579
|
+
credentials: connection.auth?.type === "env" ? `env:${connection.auth.name}` : connection.auth?.type ?? (connection.apiKeyEnv ? `env:${connection.apiKeyEnv}` : "provider default environment")
|
|
2536
2580
|
}])),
|
|
2537
2581
|
routes: Object.fromEntries(Object.entries(config.agents.routes ?? {}).map(([profile, route]) => [profile, {
|
|
2538
2582
|
runtime: route.runtime ?? "api",
|
|
@@ -4782,7 +4826,7 @@ function formatContextHits(hits, roots) {
|
|
|
4782
4826
|
}
|
|
4783
4827
|
|
|
4784
4828
|
// src/agent/runner.ts
|
|
4785
|
-
import { randomUUID as
|
|
4829
|
+
import { randomUUID as randomUUID16 } from "node:crypto";
|
|
4786
4830
|
|
|
4787
4831
|
// src/context/manager.ts
|
|
4788
4832
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
@@ -5370,7 +5414,7 @@ function concise(value, max) {
|
|
|
5370
5414
|
return normalized.length <= max ? normalized : `${normalized.slice(0, max - 1)}\u2026`;
|
|
5371
5415
|
}
|
|
5372
5416
|
function estimateMessages(messages) {
|
|
5373
|
-
return messages.reduce((sum, message2) => sum + estimateTokens(message2.content) + estimateTokens(JSON.stringify(message2.toolCalls ?? [])), 0);
|
|
5417
|
+
return messages.reduce((sum, message2) => sum + estimateTokens(message2.content) + estimateTokens(JSON.stringify(message2.toolCalls ?? [])) + estimateTokens(JSON.stringify(message2.providerMetadata ?? {})), 0);
|
|
5374
5418
|
}
|
|
5375
5419
|
function toolTokens(messages) {
|
|
5376
5420
|
return messages.filter((message2) => message2.role === "tool").reduce((sum, message2) => sum + estimateTokens(message2.content), 0);
|
|
@@ -5677,17 +5721,42 @@ function requireApiKey(config) {
|
|
|
5677
5721
|
}
|
|
5678
5722
|
return config.apiKey;
|
|
5679
5723
|
}
|
|
5680
|
-
async function parseErrorResponse(response) {
|
|
5681
|
-
const
|
|
5724
|
+
async function parseErrorResponse(response, secrets = []) {
|
|
5725
|
+
const rawDetails = await response.text();
|
|
5726
|
+
const details = sanitizeProviderErrorText(rawDetails, secrets, 2e3);
|
|
5682
5727
|
let message2 = `Model API request failed (${response.status})`;
|
|
5683
5728
|
try {
|
|
5684
|
-
const body = JSON.parse(
|
|
5729
|
+
const body = JSON.parse(rawDetails);
|
|
5685
5730
|
if (typeof body.error === "string") message2 = body.error;
|
|
5686
|
-
else if (body.error
|
|
5731
|
+
else if (body.error && typeof body.error === "object" && typeof body.error.message === "string") {
|
|
5732
|
+
message2 = body.error.message;
|
|
5733
|
+
}
|
|
5734
|
+
} catch {
|
|
5735
|
+
if (rawDetails.trim()) message2 = `${message2}: ${rawDetails}`;
|
|
5736
|
+
}
|
|
5737
|
+
throw new ProviderError(
|
|
5738
|
+
sanitizeProviderErrorText(message2, secrets, 500),
|
|
5739
|
+
response.status,
|
|
5740
|
+
details
|
|
5741
|
+
);
|
|
5742
|
+
}
|
|
5743
|
+
function sanitizeProviderErrorText(input2, secrets = [], maxLength = 500) {
|
|
5744
|
+
let value = input2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu, "").replace(/\b(sk-[A-Za-z0-9_-]{12,}|AIza[0-9A-Za-z_-]{20,})\b/gu, "[redacted-secret]").replace(/\b(authorization\s*:\s*(?:bearer\s+|basic\s+)?)\S+/giu, "$1[redacted]").replace(/\b((?:api[_-]?key|access[_-]?token|authorization|cookie|password|client[_-]?secret)\s*[:=]\s*)(?:"[^"]+"|'[^']+'|\S+)/giu, "$1[redacted]").replace(/https?:\/\/[^\s"'<>]+/giu, (candidate) => redactDiagnosticUrl(candidate));
|
|
5745
|
+
for (const secret of secrets) {
|
|
5746
|
+
if (!secret) continue;
|
|
5747
|
+
value = value.split(secret).join("[redacted-secret]");
|
|
5748
|
+
}
|
|
5749
|
+
const normalized = value.trim();
|
|
5750
|
+
return normalized.length > maxLength ? `${normalized.slice(0, Math.max(0, maxLength - 1))}\u2026` : normalized;
|
|
5751
|
+
}
|
|
5752
|
+
function redactDiagnosticUrl(candidate) {
|
|
5753
|
+
try {
|
|
5754
|
+
const url = new URL(candidate);
|
|
5755
|
+
const authentication = url.username || url.password ? "<redacted>@" : "";
|
|
5756
|
+
return `${url.protocol}//${authentication}${url.host}${url.pathname}${url.search ? "?<redacted>" : ""}${url.hash ? "#<redacted>" : ""}`;
|
|
5687
5757
|
} catch {
|
|
5688
|
-
|
|
5758
|
+
return "configured endpoint";
|
|
5689
5759
|
}
|
|
5690
|
-
throw new ProviderError(message2, response.status, details);
|
|
5691
5760
|
}
|
|
5692
5761
|
function safeJsonArguments(value) {
|
|
5693
5762
|
if (!value) return {};
|
|
@@ -5745,18 +5814,19 @@ function parseEventBlock(block) {
|
|
|
5745
5814
|
var AnthropicProvider = class {
|
|
5746
5815
|
constructor(config) {
|
|
5747
5816
|
this.config = config;
|
|
5817
|
+
this.name = config.provider === "compatible" ? "compatible" : "anthropic";
|
|
5748
5818
|
}
|
|
5749
5819
|
config;
|
|
5750
|
-
name
|
|
5820
|
+
name;
|
|
5751
5821
|
async complete(messages, tools, signal, maxOutputTokens) {
|
|
5752
|
-
const apiKey = requireApiKey(this.config);
|
|
5822
|
+
const apiKey = this.config.provider === "compatible" ? this.config.apiKey : requireApiKey(this.config);
|
|
5753
5823
|
const base = this.config.baseUrl ?? "https://api.anthropic.com/v1";
|
|
5754
5824
|
const system = messages.filter((message2) => message2.role === "system").map((message2) => message2.content).join("\n\n");
|
|
5755
|
-
const response = await fetch(
|
|
5825
|
+
const response = await fetch(anthropicMessagesEndpoint(base), {
|
|
5756
5826
|
method: "POST",
|
|
5757
5827
|
redirect: "error",
|
|
5758
5828
|
headers: {
|
|
5759
|
-
|
|
5829
|
+
...anthropicAuthHeaders(this.config, apiKey),
|
|
5760
5830
|
"anthropic-version": "2023-06-01",
|
|
5761
5831
|
"content-type": "application/json"
|
|
5762
5832
|
},
|
|
@@ -5774,7 +5844,7 @@ var AnthropicProvider = class {
|
|
|
5774
5844
|
}),
|
|
5775
5845
|
...signal ? { signal } : {}
|
|
5776
5846
|
});
|
|
5777
|
-
if (!response.ok) return parseErrorResponse(response);
|
|
5847
|
+
if (!response.ok) return parseErrorResponse(response, [apiKey]);
|
|
5778
5848
|
const data = await response.json();
|
|
5779
5849
|
const blocks = data.content ?? [];
|
|
5780
5850
|
return {
|
|
@@ -5789,14 +5859,14 @@ var AnthropicProvider = class {
|
|
|
5789
5859
|
};
|
|
5790
5860
|
}
|
|
5791
5861
|
async *stream(messages, tools, signal, maxOutputTokens) {
|
|
5792
|
-
const apiKey = requireApiKey(this.config);
|
|
5862
|
+
const apiKey = this.config.provider === "compatible" ? this.config.apiKey : requireApiKey(this.config);
|
|
5793
5863
|
const base = this.config.baseUrl ?? "https://api.anthropic.com/v1";
|
|
5794
5864
|
const system = messages.filter((message2) => message2.role === "system").map((message2) => message2.content).join("\n\n");
|
|
5795
|
-
const response = await fetch(
|
|
5865
|
+
const response = await fetch(anthropicMessagesEndpoint(base), {
|
|
5796
5866
|
method: "POST",
|
|
5797
5867
|
redirect: "error",
|
|
5798
5868
|
headers: {
|
|
5799
|
-
|
|
5869
|
+
...anthropicAuthHeaders(this.config, apiKey),
|
|
5800
5870
|
"anthropic-version": "2023-06-01",
|
|
5801
5871
|
"content-type": "application/json"
|
|
5802
5872
|
},
|
|
@@ -5815,7 +5885,7 @@ var AnthropicProvider = class {
|
|
|
5815
5885
|
}),
|
|
5816
5886
|
...signal ? { signal } : {}
|
|
5817
5887
|
});
|
|
5818
|
-
if (!response.ok) return parseErrorResponse(response);
|
|
5888
|
+
if (!response.ok) return parseErrorResponse(response, [apiKey]);
|
|
5819
5889
|
if (!response.headers.get("content-type")?.includes("text/event-stream")) {
|
|
5820
5890
|
const normalized = normalizeAnthropicResponse(await response.json());
|
|
5821
5891
|
if (normalized.content) yield { type: "text_delta", content: normalized.content };
|
|
@@ -5890,6 +5960,15 @@ var AnthropicProvider = class {
|
|
|
5890
5960
|
};
|
|
5891
5961
|
}
|
|
5892
5962
|
};
|
|
5963
|
+
function anthropicMessagesEndpoint(base) {
|
|
5964
|
+
const normalized = base.replace(/\/+$/u, "");
|
|
5965
|
+
if (normalized.endsWith("/messages")) return normalized;
|
|
5966
|
+
return normalized.endsWith("/v1") ? joinUrl(normalized, "messages") : joinUrl(normalized, "v1/messages");
|
|
5967
|
+
}
|
|
5968
|
+
function anthropicAuthHeaders(config, apiKey) {
|
|
5969
|
+
if (!apiKey) return {};
|
|
5970
|
+
return config.provider === "compatible" ? { authorization: `Bearer ${apiKey}` } : { "x-api-key": apiKey };
|
|
5971
|
+
}
|
|
5893
5972
|
function normalizeAnthropicResponse(data) {
|
|
5894
5973
|
const blocks = data.content ?? [];
|
|
5895
5974
|
return {
|
|
@@ -5973,7 +6052,7 @@ var GeminiProvider = class {
|
|
|
5973
6052
|
}),
|
|
5974
6053
|
...signal ? { signal } : {}
|
|
5975
6054
|
});
|
|
5976
|
-
if (!response.ok) return parseErrorResponse(response);
|
|
6055
|
+
if (!response.ok) return parseErrorResponse(response, [apiKey]);
|
|
5977
6056
|
const data = await response.json();
|
|
5978
6057
|
const candidate = data.candidates?.[0];
|
|
5979
6058
|
const parts = candidate?.content?.parts ?? [];
|
|
@@ -6014,7 +6093,7 @@ var GeminiProvider = class {
|
|
|
6014
6093
|
}),
|
|
6015
6094
|
...signal ? { signal } : {}
|
|
6016
6095
|
});
|
|
6017
|
-
if (!response.ok) return parseErrorResponse(response);
|
|
6096
|
+
if (!response.ok) return parseErrorResponse(response, [apiKey]);
|
|
6018
6097
|
if (!response.headers.get("content-type")?.includes("text/event-stream")) {
|
|
6019
6098
|
const normalized = normalizeGeminiResponse(await response.json());
|
|
6020
6099
|
if (normalized.content) yield { type: "text_delta", content: normalized.content };
|
|
@@ -6167,7 +6246,7 @@ var OpenAIProvider = class {
|
|
|
6167
6246
|
body: JSON.stringify(body),
|
|
6168
6247
|
...signal ? { signal } : {}
|
|
6169
6248
|
});
|
|
6170
|
-
if (!response.ok) return parseErrorResponse(response);
|
|
6249
|
+
if (!response.ok) return parseErrorResponse(response, [apiKey]);
|
|
6171
6250
|
const data = await response.json();
|
|
6172
6251
|
return normalizeOpenAIResponse(data);
|
|
6173
6252
|
}
|
|
@@ -6212,7 +6291,7 @@ var OpenAIProvider = class {
|
|
|
6212
6291
|
yield { type: "result", response: fallback };
|
|
6213
6292
|
return;
|
|
6214
6293
|
}
|
|
6215
|
-
return parseErrorResponse(response);
|
|
6294
|
+
return parseErrorResponse(response, [apiKey]);
|
|
6216
6295
|
}
|
|
6217
6296
|
if (!response.headers.get("content-type")?.includes("text/event-stream")) {
|
|
6218
6297
|
const data = await response.json();
|
|
@@ -6324,6 +6403,193 @@ function toOpenAIMessage(message2) {
|
|
|
6324
6403
|
return { role: message2.role, content: message2.content };
|
|
6325
6404
|
}
|
|
6326
6405
|
|
|
6406
|
+
// src/providers/responses.ts
|
|
6407
|
+
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
6408
|
+
var MAX_RESPONSES_OUTPUT_ITEMS = 128;
|
|
6409
|
+
var MAX_RESPONSES_REPLAY_BYTES = 4 * 1024 * 1024;
|
|
6410
|
+
var ResponsesProvider = class {
|
|
6411
|
+
constructor(config) {
|
|
6412
|
+
this.config = config;
|
|
6413
|
+
this.name = config.provider === "compatible" ? "compatible" : "openai";
|
|
6414
|
+
}
|
|
6415
|
+
config;
|
|
6416
|
+
name;
|
|
6417
|
+
async complete(messages, tools, signal, maxOutputTokens) {
|
|
6418
|
+
const response = await this.request(messages, tools, false, signal, maxOutputTokens);
|
|
6419
|
+
if (!response.ok) return parseErrorResponse(response, [this.config.apiKey]);
|
|
6420
|
+
return normalizeResponsesResponse(await response.json());
|
|
6421
|
+
}
|
|
6422
|
+
async *stream(messages, tools, signal, maxOutputTokens) {
|
|
6423
|
+
const response = await this.request(messages, tools, true, signal, maxOutputTokens);
|
|
6424
|
+
if (!response.ok) return parseErrorResponse(response, [this.config.apiKey]);
|
|
6425
|
+
if (!response.headers.get("content-type")?.includes("text/event-stream")) {
|
|
6426
|
+
const normalized2 = normalizeResponsesResponse(await response.json());
|
|
6427
|
+
if (normalized2.content) yield { type: "text_delta", content: normalized2.content };
|
|
6428
|
+
yield { type: "result", response: normalized2 };
|
|
6429
|
+
return;
|
|
6430
|
+
}
|
|
6431
|
+
let content = "";
|
|
6432
|
+
let completed;
|
|
6433
|
+
const calls = /* @__PURE__ */ new Map();
|
|
6434
|
+
for await (const event of parseServerSentEvents(response)) {
|
|
6435
|
+
if (event.data === "[DONE]") break;
|
|
6436
|
+
const chunk = JSON.parse(event.data);
|
|
6437
|
+
const eventType = chunk.type ?? event.event;
|
|
6438
|
+
if (eventType === "response.output_text.delta" && chunk.delta) {
|
|
6439
|
+
content += chunk.delta;
|
|
6440
|
+
yield { type: "text_delta", content: chunk.delta };
|
|
6441
|
+
}
|
|
6442
|
+
if (eventType === "response.output_item.added" && chunk.item?.type === "function_call") {
|
|
6443
|
+
const key = streamCallKey(chunk);
|
|
6444
|
+
calls.set(key, {
|
|
6445
|
+
id: chunk.item.call_id ?? chunk.item.id ?? randomUUID9(),
|
|
6446
|
+
name: chunk.item.name ?? "unknown",
|
|
6447
|
+
arguments: chunk.item.arguments ?? ""
|
|
6448
|
+
});
|
|
6449
|
+
}
|
|
6450
|
+
if (eventType === "response.function_call_arguments.delta") {
|
|
6451
|
+
const key = streamCallKey(chunk);
|
|
6452
|
+
const call = calls.get(key) ?? { id: chunk.item_id ?? randomUUID9(), name: "unknown", arguments: "" };
|
|
6453
|
+
call.arguments += chunk.delta ?? "";
|
|
6454
|
+
calls.set(key, call);
|
|
6455
|
+
}
|
|
6456
|
+
if (eventType === "response.output_item.done" && chunk.item?.type === "function_call") {
|
|
6457
|
+
const key = streamCallKey(chunk);
|
|
6458
|
+
const existing = calls.get(key);
|
|
6459
|
+
calls.set(key, {
|
|
6460
|
+
id: chunk.item.call_id ?? existing?.id ?? chunk.item.id ?? randomUUID9(),
|
|
6461
|
+
name: chunk.item.name ?? existing?.name ?? "unknown",
|
|
6462
|
+
arguments: chunk.item.arguments ?? existing?.arguments ?? ""
|
|
6463
|
+
});
|
|
6464
|
+
}
|
|
6465
|
+
if (eventType === "response.completed" || eventType === "response.incomplete") {
|
|
6466
|
+
completed = chunk.response;
|
|
6467
|
+
}
|
|
6468
|
+
if (eventType === "error" || eventType === "response.failed") {
|
|
6469
|
+
throw new ProviderError(sanitizeProviderErrorText(
|
|
6470
|
+
chunk.error?.message ?? chunk.response?.error?.message ?? "Model API stream failed.",
|
|
6471
|
+
[this.config.apiKey]
|
|
6472
|
+
));
|
|
6473
|
+
}
|
|
6474
|
+
}
|
|
6475
|
+
const normalized = completed ? normalizeResponsesResponse(completed) : emptyResponsesResult();
|
|
6476
|
+
const streamedCalls = [...calls.values()].map((call) => ({
|
|
6477
|
+
id: call.id,
|
|
6478
|
+
name: call.name,
|
|
6479
|
+
arguments: safeJsonArguments(call.arguments)
|
|
6480
|
+
}));
|
|
6481
|
+
yield {
|
|
6482
|
+
type: "result",
|
|
6483
|
+
response: {
|
|
6484
|
+
...normalized,
|
|
6485
|
+
content: content || normalized.content,
|
|
6486
|
+
toolCalls: normalized.toolCalls.length ? normalized.toolCalls : streamedCalls
|
|
6487
|
+
}
|
|
6488
|
+
};
|
|
6489
|
+
}
|
|
6490
|
+
async request(messages, tools, stream, signal, maxOutputTokens) {
|
|
6491
|
+
const apiKey = this.config.provider === "compatible" ? this.config.apiKey : requireApiKey(this.config);
|
|
6492
|
+
if (this.config.provider === "compatible" && !this.config.baseUrl) {
|
|
6493
|
+
throw new ProviderError("Responses-compatible providers require a baseUrl.");
|
|
6494
|
+
}
|
|
6495
|
+
const base = this.config.baseUrl ?? "https://api.openai.com/v1";
|
|
6496
|
+
const endpoint = base.endsWith("/responses") ? base : joinUrl(base, "responses");
|
|
6497
|
+
return fetch(endpoint, {
|
|
6498
|
+
method: "POST",
|
|
6499
|
+
redirect: "error",
|
|
6500
|
+
headers: {
|
|
6501
|
+
...apiKey ? { authorization: `Bearer ${apiKey}` } : {},
|
|
6502
|
+
"content-type": "application/json"
|
|
6503
|
+
},
|
|
6504
|
+
body: JSON.stringify({
|
|
6505
|
+
model: this.config.model,
|
|
6506
|
+
input: messages.flatMap(toResponsesInput),
|
|
6507
|
+
tools: tools.map((tool) => ({
|
|
6508
|
+
type: "function",
|
|
6509
|
+
name: tool.name,
|
|
6510
|
+
description: tool.description,
|
|
6511
|
+
parameters: tool.inputSchema,
|
|
6512
|
+
strict: false
|
|
6513
|
+
})),
|
|
6514
|
+
tool_choice: tools.length ? "auto" : void 0,
|
|
6515
|
+
store: false,
|
|
6516
|
+
stream,
|
|
6517
|
+
max_output_tokens: maxOutputTokens ?? this.config.maxTokens
|
|
6518
|
+
}),
|
|
6519
|
+
...signal ? { signal } : {}
|
|
6520
|
+
});
|
|
6521
|
+
}
|
|
6522
|
+
};
|
|
6523
|
+
function toResponsesInput(message2) {
|
|
6524
|
+
if (message2.role === "tool") {
|
|
6525
|
+
return [{
|
|
6526
|
+
type: "function_call_output",
|
|
6527
|
+
call_id: message2.toolCallId,
|
|
6528
|
+
output: message2.content
|
|
6529
|
+
}];
|
|
6530
|
+
}
|
|
6531
|
+
const storedOutput = message2.providerMetadata?.responses?.outputItems;
|
|
6532
|
+
if (message2.role === "assistant" && storedOutput?.length) return storedOutput;
|
|
6533
|
+
if (message2.role === "assistant" && message2.toolCalls?.length) {
|
|
6534
|
+
return [
|
|
6535
|
+
...message2.content ? [{ type: "message", role: "assistant", content: message2.content }] : [],
|
|
6536
|
+
...message2.toolCalls.map((call) => ({
|
|
6537
|
+
type: "function_call",
|
|
6538
|
+
call_id: call.id,
|
|
6539
|
+
name: call.name,
|
|
6540
|
+
arguments: JSON.stringify(call.arguments)
|
|
6541
|
+
}))
|
|
6542
|
+
];
|
|
6543
|
+
}
|
|
6544
|
+
return [{ type: "message", role: message2.role, content: message2.content }];
|
|
6545
|
+
}
|
|
6546
|
+
function normalizeResponsesResponse(data) {
|
|
6547
|
+
if (data.output !== void 0 && !Array.isArray(data.output)) {
|
|
6548
|
+
throw new ProviderError("Model API returned an invalid Responses output array.");
|
|
6549
|
+
}
|
|
6550
|
+
const output2 = data.output ?? [];
|
|
6551
|
+
if (output2.some((item) => !item || typeof item !== "object" || Array.isArray(item))) {
|
|
6552
|
+
throw new ProviderError("Model API returned an invalid Responses output item.");
|
|
6553
|
+
}
|
|
6554
|
+
const toolCalls = output2.filter((item) => item.type === "function_call").map((item) => ({
|
|
6555
|
+
id: item.call_id ?? item.id ?? randomUUID9(),
|
|
6556
|
+
name: item.name ?? "unknown",
|
|
6557
|
+
arguments: safeJsonArguments(item.arguments)
|
|
6558
|
+
}));
|
|
6559
|
+
const outputText = output2.flatMap((item) => item.type === "message" ? (item.content ?? []).flatMap((part) => part.type === "output_text" && typeof part.text === "string" ? [part.text] : []) : []).join("");
|
|
6560
|
+
return {
|
|
6561
|
+
content: data.output_text ?? outputText,
|
|
6562
|
+
toolCalls,
|
|
6563
|
+
usage: normalizeResponsesUsage(data.usage),
|
|
6564
|
+
...output2.length ? { providerMetadata: { responses: { outputItems: boundedOutputItems(output2) } } } : {},
|
|
6565
|
+
...toolCalls.length ? { stopReason: "tool_calls" } : data.incomplete_details?.reason ? { stopReason: data.incomplete_details.reason } : data.status ? { stopReason: data.status } : {}
|
|
6566
|
+
};
|
|
6567
|
+
}
|
|
6568
|
+
function boundedOutputItems(output2) {
|
|
6569
|
+
if (output2.length > MAX_RESPONSES_OUTPUT_ITEMS) {
|
|
6570
|
+
throw new ProviderError(`Model API returned more than ${MAX_RESPONSES_OUTPUT_ITEMS} Responses output items.`);
|
|
6571
|
+
}
|
|
6572
|
+
const serialized = JSON.stringify(output2);
|
|
6573
|
+
if (new TextEncoder().encode(serialized).byteLength > MAX_RESPONSES_REPLAY_BYTES) {
|
|
6574
|
+
throw new ProviderError("Model API returned Responses replay state larger than the 4 MiB safety limit.");
|
|
6575
|
+
}
|
|
6576
|
+
return JSON.parse(serialized);
|
|
6577
|
+
}
|
|
6578
|
+
function normalizeResponsesUsage(usage) {
|
|
6579
|
+
return {
|
|
6580
|
+
...usage?.input_tokens !== void 0 ? { inputTokens: usage.input_tokens } : {},
|
|
6581
|
+
...usage?.output_tokens !== void 0 ? { outputTokens: usage.output_tokens } : {},
|
|
6582
|
+
...usage?.input_tokens_details?.cached_tokens !== void 0 ? { cachedInputTokens: usage.input_tokens_details.cached_tokens } : {},
|
|
6583
|
+
...usage?.output_tokens_details?.reasoning_tokens !== void 0 ? { reasoningTokens: usage.output_tokens_details.reasoning_tokens } : {}
|
|
6584
|
+
};
|
|
6585
|
+
}
|
|
6586
|
+
function emptyResponsesResult() {
|
|
6587
|
+
return { content: "", toolCalls: [], usage: {} };
|
|
6588
|
+
}
|
|
6589
|
+
function streamCallKey(chunk) {
|
|
6590
|
+
return chunk.item_id ?? chunk.item?.id ?? `output:${chunk.output_index ?? 0}`;
|
|
6591
|
+
}
|
|
6592
|
+
|
|
6327
6593
|
// src/providers/index.ts
|
|
6328
6594
|
function createProvider(config) {
|
|
6329
6595
|
switch (config.provider) {
|
|
@@ -6332,13 +6598,17 @@ function createProvider(config) {
|
|
|
6332
6598
|
case "gemini":
|
|
6333
6599
|
return new GeminiProvider(config);
|
|
6334
6600
|
case "openai":
|
|
6601
|
+
return new OpenAIProvider(config);
|
|
6335
6602
|
case "compatible":
|
|
6603
|
+
if (config.protocol === "openai-responses") return new ResponsesProvider(config);
|
|
6604
|
+
if (config.protocol === "anthropic-messages") return new AnthropicProvider(config);
|
|
6605
|
+
if (config.protocol === "gemini") throw new Error("Compatible relay connections cannot use the Gemini transport.");
|
|
6336
6606
|
return new OpenAIProvider(config);
|
|
6337
6607
|
}
|
|
6338
6608
|
}
|
|
6339
6609
|
|
|
6340
6610
|
// src/checkpoint/store.ts
|
|
6341
|
-
import { createHash as createHash8, randomUUID as
|
|
6611
|
+
import { createHash as createHash8, randomUUID as randomUUID10 } from "node:crypto";
|
|
6342
6612
|
import { lstat as lstat10, mkdir as mkdir7, readFile as readFile5, readdir as readdir2, unlink as unlink2 } from "node:fs/promises";
|
|
6343
6613
|
import { basename as basename6, dirname as dirname8, join as join9, relative as relative6, resolve as resolve12 } from "node:path";
|
|
6344
6614
|
import { z as z4 } from "zod";
|
|
@@ -6374,7 +6644,7 @@ var CheckpointStore = class {
|
|
|
6374
6644
|
return this.withManagedLease(() => this.captureUnlocked(sessionId, unique4, options));
|
|
6375
6645
|
}
|
|
6376
6646
|
async captureUnlocked(sessionId, unique4, options) {
|
|
6377
|
-
const id = `${Date.now().toString(36)}-${
|
|
6647
|
+
const id = `${Date.now().toString(36)}-${randomUUID10()}`;
|
|
6378
6648
|
const target = join9(this.directory, sessionId, id);
|
|
6379
6649
|
const blobDirectory = join9(target, "blobs");
|
|
6380
6650
|
await this.ensureDirectory();
|
|
@@ -6650,7 +6920,7 @@ var HookRunner = class {
|
|
|
6650
6920
|
};
|
|
6651
6921
|
|
|
6652
6922
|
// src/session/store.ts
|
|
6653
|
-
import { randomUUID as
|
|
6923
|
+
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
6654
6924
|
import { constants as constants4 } from "node:fs";
|
|
6655
6925
|
import {
|
|
6656
6926
|
chmod as chmod6,
|
|
@@ -6672,6 +6942,15 @@ var toolCallSchema = z5.object({
|
|
|
6672
6942
|
name: z5.string(),
|
|
6673
6943
|
arguments: z5.record(z5.string(), z5.unknown())
|
|
6674
6944
|
}).strict();
|
|
6945
|
+
var providerMetadataSchema = z5.object({
|
|
6946
|
+
responses: z5.object({
|
|
6947
|
+
outputItems: z5.array(z5.record(z5.string(), z5.unknown())).max(128).superRefine((items, context) => {
|
|
6948
|
+
if (new TextEncoder().encode(JSON.stringify(items)).byteLength > 4 * 1024 * 1024) {
|
|
6949
|
+
context.addIssue({ code: "custom", message: "Responses replay state exceeds 4 MiB" });
|
|
6950
|
+
}
|
|
6951
|
+
})
|
|
6952
|
+
}).strict().optional()
|
|
6953
|
+
}).strict();
|
|
6675
6954
|
var messageSchema = z5.object({
|
|
6676
6955
|
id: z5.string(),
|
|
6677
6956
|
role: z5.enum(["system", "user", "assistant", "tool"]),
|
|
@@ -6679,7 +6958,8 @@ var messageSchema = z5.object({
|
|
|
6679
6958
|
createdAt: z5.string(),
|
|
6680
6959
|
toolCalls: z5.array(toolCallSchema).optional(),
|
|
6681
6960
|
toolCallId: z5.string().optional(),
|
|
6682
|
-
name: z5.string().optional()
|
|
6961
|
+
name: z5.string().optional(),
|
|
6962
|
+
providerMetadata: providerMetadataSchema.optional()
|
|
6683
6963
|
}).strict();
|
|
6684
6964
|
var taskSchema = z5.object({
|
|
6685
6965
|
id: z5.string(),
|
|
@@ -7115,7 +7395,7 @@ var SessionStore = class {
|
|
|
7115
7395
|
const backup = this.backupPathFor(session.id);
|
|
7116
7396
|
await this.assertManagedFile(target);
|
|
7117
7397
|
await this.assertManagedFile(backup);
|
|
7118
|
-
const temporary = join10(this.directory, `.${session.id}.${
|
|
7398
|
+
const temporary = join10(this.directory, `.${session.id}.${randomUUID11()}.tmp`);
|
|
7119
7399
|
const data = `${JSON.stringify(session, null, 2)}
|
|
7120
7400
|
`;
|
|
7121
7401
|
const handle = await open2(temporary, "wx", 384);
|
|
@@ -7133,7 +7413,7 @@ var SessionStore = class {
|
|
|
7133
7413
|
}
|
|
7134
7414
|
}
|
|
7135
7415
|
async copyBackup(source, target) {
|
|
7136
|
-
const temporary = join10(this.directory, `.${basename7(target)}.${
|
|
7416
|
+
const temporary = join10(this.directory, `.${basename7(target)}.${randomUUID11()}.tmp`);
|
|
7137
7417
|
try {
|
|
7138
7418
|
await copyFile(source, temporary, constants4.COPYFILE_EXCL);
|
|
7139
7419
|
await chmod6(temporary, 384);
|
|
@@ -7222,7 +7502,7 @@ var SessionStore = class {
|
|
|
7222
7502
|
}
|
|
7223
7503
|
};
|
|
7224
7504
|
function createSession(options) {
|
|
7225
|
-
const id = options.id ??
|
|
7505
|
+
const id = options.id ?? randomUUID11();
|
|
7226
7506
|
validateId(id);
|
|
7227
7507
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7228
7508
|
return {
|
|
@@ -7238,7 +7518,7 @@ function createSession(options) {
|
|
|
7238
7518
|
changedFiles: [],
|
|
7239
7519
|
audit: [],
|
|
7240
7520
|
contextEpochs: [{
|
|
7241
|
-
id:
|
|
7521
|
+
id: randomUUID11(),
|
|
7242
7522
|
index: 1,
|
|
7243
7523
|
startedAt: now,
|
|
7244
7524
|
usage: { inputTokens: 0, outputTokens: 0 }
|
|
@@ -9093,7 +9373,7 @@ async function discoverSnapshotFiles(root) {
|
|
|
9093
9373
|
}
|
|
9094
9374
|
|
|
9095
9375
|
// src/tools/task.ts
|
|
9096
|
-
import { randomUUID as
|
|
9376
|
+
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
9097
9377
|
import { z as z14 } from "zod";
|
|
9098
9378
|
var taskSchema2 = z14.object({
|
|
9099
9379
|
id: z14.string().min(1).optional(),
|
|
@@ -9142,7 +9422,7 @@ var taskTool = {
|
|
|
9142
9422
|
case "list":
|
|
9143
9423
|
break;
|
|
9144
9424
|
case "add":
|
|
9145
|
-
tasks.push({ id:
|
|
9425
|
+
tasks.push({ id: randomUUID12(), title: input2.title, status: input2.status ?? "pending" });
|
|
9146
9426
|
break;
|
|
9147
9427
|
case "update": {
|
|
9148
9428
|
const task = tasks.find((item) => item.id === input2.id);
|
|
@@ -9159,7 +9439,7 @@ var taskTool = {
|
|
|
9159
9439
|
}
|
|
9160
9440
|
case "replace":
|
|
9161
9441
|
tasks.splice(0, tasks.length, ...input2.tasks.map((task) => ({
|
|
9162
|
-
id: task.id ??
|
|
9442
|
+
id: task.id ?? randomUUID12(),
|
|
9163
9443
|
title: task.title,
|
|
9164
9444
|
status: task.status
|
|
9165
9445
|
})));
|
|
@@ -9173,11 +9453,11 @@ var taskTool = {
|
|
|
9173
9453
|
};
|
|
9174
9454
|
|
|
9175
9455
|
// src/tools/task-contract.ts
|
|
9176
|
-
import { randomUUID as
|
|
9456
|
+
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
9177
9457
|
import { z as z15 } from "zod";
|
|
9178
9458
|
|
|
9179
9459
|
// src/agent/task-contract.ts
|
|
9180
|
-
import { randomUUID as
|
|
9460
|
+
import { randomUUID as randomUUID13 } from "node:crypto";
|
|
9181
9461
|
var EXECUTABLE_INTENTS = /* @__PURE__ */ new Set(["debug", "refactor", "test", "implement"]);
|
|
9182
9462
|
function shouldUseTaskContract(request, intent, existing) {
|
|
9183
9463
|
if (existing && existing.state !== "satisfied") return true;
|
|
@@ -9243,7 +9523,7 @@ function refreshTaskContractState(contract) {
|
|
|
9243
9523
|
}
|
|
9244
9524
|
function criterion(id, description) {
|
|
9245
9525
|
return {
|
|
9246
|
-
id: `${id}-${
|
|
9526
|
+
id: `${id}-${randomUUID13().slice(0, 8)}`,
|
|
9247
9527
|
description,
|
|
9248
9528
|
required: true,
|
|
9249
9529
|
status: "pending",
|
|
@@ -9797,7 +10077,7 @@ var taskContractTool = {
|
|
|
9797
10077
|
}
|
|
9798
10078
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9799
10079
|
const criteria = input2.acceptance_criteria.map((item) => ({
|
|
9800
|
-
id: item.id ?? `criterion-${
|
|
10080
|
+
id: item.id ?? `criterion-${randomUUID14().slice(0, 8)}`,
|
|
9801
10081
|
description: item.description,
|
|
9802
10082
|
required: item.required ?? true,
|
|
9803
10083
|
status: "pending",
|
|
@@ -10266,7 +10546,7 @@ function escapeAttribute3(value) {
|
|
|
10266
10546
|
}
|
|
10267
10547
|
|
|
10268
10548
|
// src/agent/intent-sufficiency.ts
|
|
10269
|
-
import { randomUUID as
|
|
10549
|
+
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
10270
10550
|
function assessIntentSufficiency(request, intent, context) {
|
|
10271
10551
|
const assessedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10272
10552
|
const uiChoice = explicitUiChoice(request);
|
|
@@ -10334,8 +10614,8 @@ function assessment(route, reason, assessedAt, retrievalHits) {
|
|
|
10334
10614
|
}
|
|
10335
10615
|
function clarification(originalRequest, createdAt, reason, question2, options) {
|
|
10336
10616
|
return {
|
|
10337
|
-
id:
|
|
10338
|
-
runId:
|
|
10617
|
+
id: randomUUID15(),
|
|
10618
|
+
runId: randomUUID15(),
|
|
10339
10619
|
createdAt,
|
|
10340
10620
|
originalRequest: originalRequest.slice(0, 12e4),
|
|
10341
10621
|
question: question2,
|
|
@@ -11588,7 +11868,7 @@ ${resolvedInput.decision}
|
|
|
11588
11868
|
breakdown
|
|
11589
11869
|
});
|
|
11590
11870
|
}
|
|
11591
|
-
const assistantId =
|
|
11871
|
+
const assistantId = randomUUID16();
|
|
11592
11872
|
const response = await this.completeModel(
|
|
11593
11873
|
messages,
|
|
11594
11874
|
visibleTools,
|
|
@@ -11598,7 +11878,8 @@ ${resolvedInput.decision}
|
|
|
11598
11878
|
assistantId
|
|
11599
11879
|
);
|
|
11600
11880
|
const assistantMessage = message("assistant", response.content, {
|
|
11601
|
-
...response.toolCalls.length ? { toolCalls: response.toolCalls } : {}
|
|
11881
|
+
...response.toolCalls.length ? { toolCalls: response.toolCalls } : {},
|
|
11882
|
+
...response.providerMetadata ? { providerMetadata: response.providerMetadata } : {}
|
|
11602
11883
|
});
|
|
11603
11884
|
assistantMessage.id = assistantId;
|
|
11604
11885
|
this.session.messages.push(assistantMessage);
|
|
@@ -12089,7 +12370,7 @@ ${completeContent}`;
|
|
|
12089
12370
|
}
|
|
12090
12371
|
appendAudit(event) {
|
|
12091
12372
|
const audit = this.session.audit ?? (this.session.audit = []);
|
|
12092
|
-
audit.push({ id:
|
|
12373
|
+
audit.push({ id: randomUUID16(), createdAt: (/* @__PURE__ */ new Date()).toISOString(), ...event });
|
|
12093
12374
|
if (audit.length > 5e3) audit.splice(0, audit.length - 5e3);
|
|
12094
12375
|
}
|
|
12095
12376
|
async acceptChangedFiles(paths) {
|
|
@@ -12110,7 +12391,7 @@ ${completeContent}`;
|
|
|
12110
12391
|
const results = [];
|
|
12111
12392
|
for (const command2 of this.config.agent.verifyCommands) {
|
|
12112
12393
|
const call = {
|
|
12113
|
-
id: `verify-${
|
|
12394
|
+
id: `verify-${randomUUID16()}`,
|
|
12114
12395
|
name: "shell",
|
|
12115
12396
|
arguments: { command: command2, cwd: this.workspace.primaryRoot }
|
|
12116
12397
|
};
|
|
@@ -12276,7 +12557,7 @@ ${completeContent}`;
|
|
|
12276
12557
|
};
|
|
12277
12558
|
function message(role, content, extra = {}) {
|
|
12278
12559
|
return {
|
|
12279
|
-
id:
|
|
12560
|
+
id: randomUUID16(),
|
|
12280
12561
|
role,
|
|
12281
12562
|
content,
|
|
12282
12563
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -12294,7 +12575,7 @@ function packConversation(systemPrompt, dynamicPrompt, retrievedContext, history
|
|
|
12294
12575
|
let used = 0;
|
|
12295
12576
|
for (let index = groups.length - 1; index >= 0; index -= 1) {
|
|
12296
12577
|
const group = groups[index] ?? [];
|
|
12297
|
-
const cost = group.reduce((sum, item) => sum + estimateTokens(item.content) + estimateTokens(JSON.stringify(item.toolCalls ?? [])), 0);
|
|
12578
|
+
const cost = group.reduce((sum, item) => sum + estimateTokens(item.content) + estimateTokens(JSON.stringify(item.toolCalls ?? [])) + estimateTokens(JSON.stringify(item.providerMetadata ?? {})), 0);
|
|
12298
12579
|
if (selected.length && used + cost > budget) break;
|
|
12299
12580
|
selected.unshift(group);
|
|
12300
12581
|
used += cost;
|
|
@@ -12333,7 +12614,7 @@ function groupMessages(messages) {
|
|
|
12333
12614
|
return groups;
|
|
12334
12615
|
}
|
|
12335
12616
|
function estimateMessages2(messages) {
|
|
12336
|
-
return messages.reduce((total, item) => total + estimateTokens(item.content) + estimateTokens(JSON.stringify(item.toolCalls ?? [])), 0);
|
|
12617
|
+
return messages.reduce((total, item) => total + estimateTokens(item.content) + estimateTokens(JSON.stringify(item.toolCalls ?? [])) + estimateTokens(JSON.stringify(item.providerMetadata ?? {})), 0);
|
|
12337
12618
|
}
|
|
12338
12619
|
function estimateToolDefinitions(tools) {
|
|
12339
12620
|
return estimateTokens(JSON.stringify(tools));
|
|
@@ -12713,7 +12994,7 @@ function integer(value, fallback, min, max) {
|
|
|
12713
12994
|
}
|
|
12714
12995
|
|
|
12715
12996
|
// src/agent/delegation.ts
|
|
12716
|
-
import { randomUUID as
|
|
12997
|
+
import { randomUUID as randomUUID18 } from "node:crypto";
|
|
12717
12998
|
import { join as join18 } from "node:path";
|
|
12718
12999
|
import { z as z19 } from "zod";
|
|
12719
13000
|
|
|
@@ -12724,6 +13005,8 @@ async function runExternalAgent(request) {
|
|
|
12724
13005
|
if (!executable2) throw new Error(`${command2.binary} CLI is not installed or resolves inside the workspace.`);
|
|
12725
13006
|
const result = await runProcess(executable2.executable, command2.args, {
|
|
12726
13007
|
cwd: request.workspace,
|
|
13008
|
+
inheritEnv: false,
|
|
13009
|
+
env: externalRuntimeEnvironment(request.runtime, executable2.path),
|
|
12727
13010
|
timeoutMs: request.timeoutMs ?? 18e4,
|
|
12728
13011
|
maxOutputBytes: 2e6,
|
|
12729
13012
|
...request.signal ? { signal: request.signal } : {}
|
|
@@ -12744,6 +13027,35 @@ async function runExternalAgent(request) {
|
|
|
12744
13027
|
toolCalls: telemetry.toolCalls
|
|
12745
13028
|
};
|
|
12746
13029
|
}
|
|
13030
|
+
function externalRuntimeEnvironment(runtime, safePath, environment = process.env) {
|
|
13031
|
+
const allowed = [
|
|
13032
|
+
"HOME",
|
|
13033
|
+
"USERPROFILE",
|
|
13034
|
+
"APPDATA",
|
|
13035
|
+
"LOCALAPPDATA",
|
|
13036
|
+
"XDG_CONFIG_HOME",
|
|
13037
|
+
"XDG_CACHE_HOME",
|
|
13038
|
+
"TMPDIR",
|
|
13039
|
+
"TMP",
|
|
13040
|
+
"TEMP",
|
|
13041
|
+
"LANG",
|
|
13042
|
+
"LC_ALL",
|
|
13043
|
+
"LC_CTYPE",
|
|
13044
|
+
"TERM",
|
|
13045
|
+
"NO_COLOR",
|
|
13046
|
+
"SystemRoot",
|
|
13047
|
+
"WINDIR",
|
|
13048
|
+
"COMSPEC",
|
|
13049
|
+
"PATHEXT"
|
|
13050
|
+
];
|
|
13051
|
+
if (runtime === "codex") allowed.push("CODEX_HOME");
|
|
13052
|
+
if (runtime === "claude") allowed.push("CLAUDE_CONFIG_DIR");
|
|
13053
|
+
const selected = { PATH: safePath };
|
|
13054
|
+
for (const name of allowed) {
|
|
13055
|
+
if (environment[name] !== void 0) selected[name] = environment[name];
|
|
13056
|
+
}
|
|
13057
|
+
return selected;
|
|
13058
|
+
}
|
|
12747
13059
|
function externalAgentCommand(request) {
|
|
12748
13060
|
const prompt = request.prompt.slice(0, 6e4);
|
|
12749
13061
|
switch (request.runtime) {
|
|
@@ -12842,7 +13154,7 @@ function numeric(...values) {
|
|
|
12842
13154
|
}
|
|
12843
13155
|
|
|
12844
13156
|
// src/agent/team-store.ts
|
|
12845
|
-
import { createHash as createHash16, randomUUID as
|
|
13157
|
+
import { createHash as createHash16, randomUUID as randomUUID17 } from "node:crypto";
|
|
12846
13158
|
import { lstat as lstat18, readFile as readFile17, readdir as readdir7, rm as rm3 } from "node:fs/promises";
|
|
12847
13159
|
import { join as join16, resolve as resolve18 } from "node:path";
|
|
12848
13160
|
import { z as z18 } from "zod";
|
|
@@ -12936,7 +13248,7 @@ var TeamRunStore = class {
|
|
|
12936
13248
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12937
13249
|
const manifest = manifestSchema2.parse({
|
|
12938
13250
|
version: 2,
|
|
12939
|
-
id:
|
|
13251
|
+
id: randomUUID17(),
|
|
12940
13252
|
workspace: this.workspace,
|
|
12941
13253
|
objective: input2.objective,
|
|
12942
13254
|
reviewer: input2.reviewer,
|
|
@@ -13183,15 +13495,285 @@ function resolveAgentModelRoute(team, parent, profile) {
|
|
|
13183
13495
|
const hasDefaults = team?.defaultConnection !== void 0 || team?.defaultModel !== void 0;
|
|
13184
13496
|
if (!configured && !hasDefaults) return { source: "parent" };
|
|
13185
13497
|
const connection = configured?.connection ?? (configured?.provider ? void 0 : team?.defaultConnection);
|
|
13498
|
+
const connectionDefaultModel = connection ? team?.connections?.[connection]?.defaultModel : void 0;
|
|
13186
13499
|
const route = {
|
|
13187
13500
|
...configured,
|
|
13188
|
-
model: configured?.model ?? team?.defaultModel ?? parent.model,
|
|
13501
|
+
model: configured?.model ?? connectionDefaultModel ?? team?.defaultModel ?? parent.model,
|
|
13189
13502
|
...connection ? { connection } : {}
|
|
13190
13503
|
};
|
|
13191
13504
|
if (!route.connection && !route.provider) route.provider = parent.provider;
|
|
13192
13505
|
return { route, source: configured ? "profile" : "default" };
|
|
13193
13506
|
}
|
|
13194
13507
|
|
|
13508
|
+
// src/agent/connection-catalog.ts
|
|
13509
|
+
var relayProtocols = ["openai-responses", "openai-chat", "anthropic-messages"];
|
|
13510
|
+
function discoverConnectionCatalog(config, environment = process.env) {
|
|
13511
|
+
const byId = /* @__PURE__ */ new Map();
|
|
13512
|
+
for (const [id, connection] of Object.entries(config.agents?.connections ?? {})) {
|
|
13513
|
+
byId.set(id, normalizeProfile(id, connection, "user", environment));
|
|
13514
|
+
}
|
|
13515
|
+
const environmentCatalog = parseEnvironmentConnections(environment);
|
|
13516
|
+
for (const profile of environmentCatalog.profiles) {
|
|
13517
|
+
if (byId.has(profile.id)) {
|
|
13518
|
+
throw new Error(`Connection ${profile.id} is defined in both user configuration and SKEIN_CONNECTIONS.`);
|
|
13519
|
+
}
|
|
13520
|
+
byId.set(profile.id, profile);
|
|
13521
|
+
}
|
|
13522
|
+
const defaultConnection = config.agents?.defaultConnection ?? environmentCatalog.defaultConnection;
|
|
13523
|
+
if (defaultConnection && !byId.has(defaultConnection)) {
|
|
13524
|
+
throw new Error(`Default connection ${defaultConnection} is not present in the discovered connection catalog.`);
|
|
13525
|
+
}
|
|
13526
|
+
return {
|
|
13527
|
+
profiles: [...byId.values()].sort((left, right) => left.id.localeCompare(right.id)),
|
|
13528
|
+
...defaultConnection ? { defaultConnection } : {}
|
|
13529
|
+
};
|
|
13530
|
+
}
|
|
13531
|
+
function parseEnvironmentConnections(environment = process.env) {
|
|
13532
|
+
const rawIds = environment.SKEIN_CONNECTIONS?.split(",").map((value) => value.trim()).filter(Boolean) ?? [];
|
|
13533
|
+
const ids = /* @__PURE__ */ new Set();
|
|
13534
|
+
const suffixes = /* @__PURE__ */ new Map();
|
|
13535
|
+
for (const id of rawIds) {
|
|
13536
|
+
if (!/^[a-z][a-z0-9_-]{0,63}$/u.test(id)) {
|
|
13537
|
+
throw new Error(`Invalid SKEIN_CONNECTIONS id ${JSON.stringify(id)}; use lowercase shell-safe names.`);
|
|
13538
|
+
}
|
|
13539
|
+
if (ids.has(id)) throw new Error(`Duplicate SKEIN_CONNECTIONS id ${id}.`);
|
|
13540
|
+
ids.add(id);
|
|
13541
|
+
const suffix = id.toUpperCase().replace(/-/gu, "_");
|
|
13542
|
+
const collided = suffixes.get(suffix);
|
|
13543
|
+
if (collided) {
|
|
13544
|
+
throw new Error(`Connection ids ${collided} and ${id} collide after environment normalization (${suffix}).`);
|
|
13545
|
+
}
|
|
13546
|
+
suffixes.set(suffix, id);
|
|
13547
|
+
}
|
|
13548
|
+
const profiles = rawIds.map((id) => {
|
|
13549
|
+
const suffix = id.toUpperCase().replace(/-/gu, "_");
|
|
13550
|
+
const prefix = `SKEIN_CONNECTION_${suffix}_`;
|
|
13551
|
+
const provider = parseProviderField(environment[`${prefix}PROVIDER`], `${prefix}PROVIDER`);
|
|
13552
|
+
const protocol = parseProtocolField(environment[`${prefix}PROTOCOL`], provider, `${prefix}PROTOCOL`);
|
|
13553
|
+
const baseUrl = optionalUrl(environment[`${prefix}BASE_URL`], `${prefix}BASE_URL`);
|
|
13554
|
+
const modelsBaseUrl = optionalUrl(environment[`${prefix}MODELS_BASE_URL`], `${prefix}MODELS_BASE_URL`);
|
|
13555
|
+
const apiKeyEnv = optionalEnvironmentName(environment[`${prefix}API_KEY_ENV`], `${prefix}API_KEY_ENV`);
|
|
13556
|
+
const authValue = environment[`${prefix}AUTH`]?.trim().toLowerCase();
|
|
13557
|
+
if (authValue && authValue !== "env" && authValue !== "none") {
|
|
13558
|
+
throw new Error(`${prefix}AUTH must be env or none.`);
|
|
13559
|
+
}
|
|
13560
|
+
if (authValue === "none" && apiKeyEnv) {
|
|
13561
|
+
throw new Error(`${prefix}AUTH=none cannot be combined with ${prefix}API_KEY_ENV.`);
|
|
13562
|
+
}
|
|
13563
|
+
if (authValue === "env" && !apiKeyEnv) {
|
|
13564
|
+
throw new Error(`${prefix}AUTH=env requires ${prefix}API_KEY_ENV.`);
|
|
13565
|
+
}
|
|
13566
|
+
const label = environment[`${prefix}LABEL`]?.trim();
|
|
13567
|
+
const defaultModel = environment[`${prefix}MODEL`]?.trim();
|
|
13568
|
+
const config = {
|
|
13569
|
+
provider,
|
|
13570
|
+
protocol,
|
|
13571
|
+
...label ? { label } : {},
|
|
13572
|
+
...baseUrl ? { baseUrl } : {},
|
|
13573
|
+
...modelsBaseUrl ? { modelsBaseUrl } : {},
|
|
13574
|
+
...defaultModel ? { defaultModel } : {},
|
|
13575
|
+
...authValue === "none" ? { auth: { type: "none" } } : apiKeyEnv ? { auth: { type: "env", name: apiKeyEnv } } : {}
|
|
13576
|
+
};
|
|
13577
|
+
return normalizeProfile(id, config, "environment", environment);
|
|
13578
|
+
});
|
|
13579
|
+
const defaultConnection = environment.SKEIN_DEFAULT_CONNECTION?.trim();
|
|
13580
|
+
if (defaultConnection && !ids.has(defaultConnection)) {
|
|
13581
|
+
throw new Error(`SKEIN_DEFAULT_CONNECTION references undiscovered connection ${defaultConnection}.`);
|
|
13582
|
+
}
|
|
13583
|
+
return { profiles, ...defaultConnection ? { defaultConnection } : {} };
|
|
13584
|
+
}
|
|
13585
|
+
function planConnectionSelection(catalog, environment = process.env, explicitConnection) {
|
|
13586
|
+
if (explicitConnection) {
|
|
13587
|
+
const selected = catalog.profiles.find((profile) => profile.id === explicitConnection);
|
|
13588
|
+
if (!selected) throw new Error(`Unknown connection ${explicitConnection}. Use ${catalog.profiles.map(({ id }) => id).join(", ") || "a configured name"}.`);
|
|
13589
|
+
return { kind: "selected", profile: selected };
|
|
13590
|
+
}
|
|
13591
|
+
if (catalog.defaultConnection) {
|
|
13592
|
+
const selected = catalog.profiles.find((profile) => profile.id === catalog.defaultConnection);
|
|
13593
|
+
if (!selected) throw new Error(`Default connection ${catalog.defaultConnection} is unavailable.`);
|
|
13594
|
+
return { kind: "selected", profile: selected };
|
|
13595
|
+
}
|
|
13596
|
+
const complete = catalog.profiles.filter((profile) => connectionIssues(profile, environment).length === 0);
|
|
13597
|
+
if (complete.length === 1) return { kind: "selected", profile: complete[0] };
|
|
13598
|
+
if (complete.length > 1) return { kind: "ambiguous", profiles: complete };
|
|
13599
|
+
return { kind: "legacy" };
|
|
13600
|
+
}
|
|
13601
|
+
function resolveConnectionModel(current, profile, overrides = {}, environment = process.env) {
|
|
13602
|
+
const issues = connectionIssues(profile, environment);
|
|
13603
|
+
if (issues.length) throw new Error(`Connection ${profile.id} is incomplete: ${issues.join("; ")}`);
|
|
13604
|
+
const apiKey = profile.auth.type === "env" ? environment[profile.auth.name] : void 0;
|
|
13605
|
+
const model = {
|
|
13606
|
+
provider: profile.provider,
|
|
13607
|
+
protocol: profile.protocol,
|
|
13608
|
+
model: overrides.model ?? profile.defaultModel ?? defaultModelForProvider(profile.provider),
|
|
13609
|
+
...current.temperature !== void 0 ? { temperature: current.temperature } : {},
|
|
13610
|
+
...current.maxTokens !== void 0 ? { maxTokens: current.maxTokens } : {},
|
|
13611
|
+
...profile.baseUrl ? { baseUrl: profile.baseUrl } : {},
|
|
13612
|
+
...apiKey ? { apiKey } : {}
|
|
13613
|
+
};
|
|
13614
|
+
return { model, activeConnection: connectionRuntimeInfo(profile, environment) };
|
|
13615
|
+
}
|
|
13616
|
+
function connectionRuntimeCatalog(catalog, environment = process.env) {
|
|
13617
|
+
return {
|
|
13618
|
+
...catalog.defaultConnection ? { defaultConnection: catalog.defaultConnection } : {},
|
|
13619
|
+
profiles: catalog.profiles.map((profile) => connectionRuntimeInfo(profile, environment))
|
|
13620
|
+
};
|
|
13621
|
+
}
|
|
13622
|
+
function connectionRuntimeInfo(profile, environment = process.env) {
|
|
13623
|
+
const issues = connectionIssues(profile, environment);
|
|
13624
|
+
const authStatus = profile.auth.type === "none" ? "none" : environment[profile.auth.name] ? "configured" : "missing";
|
|
13625
|
+
return {
|
|
13626
|
+
id: profile.id,
|
|
13627
|
+
...profile.label ? { label: profile.label } : {},
|
|
13628
|
+
provider: profile.provider,
|
|
13629
|
+
protocol: profile.protocol,
|
|
13630
|
+
source: profile.source,
|
|
13631
|
+
endpoint: redactConnectionEndpoint(profile.baseUrl),
|
|
13632
|
+
modelsEndpoint: redactConnectionEndpoint(profile.modelsBaseUrl ?? profile.baseUrl),
|
|
13633
|
+
...profile.defaultModel ? { defaultModel: profile.defaultModel } : {},
|
|
13634
|
+
authType: profile.auth.type,
|
|
13635
|
+
authStatus,
|
|
13636
|
+
complete: issues.length === 0,
|
|
13637
|
+
issues
|
|
13638
|
+
};
|
|
13639
|
+
}
|
|
13640
|
+
function legacyConnectionRuntimeInfo(model) {
|
|
13641
|
+
const remoteCompatibleMissingCredential = model.provider === "compatible" && Boolean(model.baseUrl) && !isLoopbackEndpoint2(model.baseUrl) && !model.apiKey;
|
|
13642
|
+
const issues = [
|
|
13643
|
+
...model.provider === "compatible" && !model.baseUrl ? ["compatible provider requires base URL"] : [],
|
|
13644
|
+
...remoteCompatibleMissingCredential ? ["remote compatible provider credential is not configured"] : [],
|
|
13645
|
+
...model.provider !== "compatible" && !model.apiKey ? ["provider credential is not configured"] : []
|
|
13646
|
+
];
|
|
13647
|
+
const authExpected = model.provider !== "compatible" || remoteCompatibleMissingCredential || Boolean(model.apiKey);
|
|
13648
|
+
return {
|
|
13649
|
+
id: "legacy",
|
|
13650
|
+
provider: model.provider,
|
|
13651
|
+
protocol: model.protocol ?? defaultProtocol(model.provider),
|
|
13652
|
+
source: "legacy",
|
|
13653
|
+
endpoint: redactConnectionEndpoint(model.baseUrl),
|
|
13654
|
+
modelsEndpoint: redactConnectionEndpoint(model.baseUrl),
|
|
13655
|
+
defaultModel: model.model,
|
|
13656
|
+
authType: authExpected ? "env" : "none",
|
|
13657
|
+
authStatus: model.apiKey ? "configured" : authExpected ? "missing" : "none",
|
|
13658
|
+
complete: issues.length === 0,
|
|
13659
|
+
issues
|
|
13660
|
+
};
|
|
13661
|
+
}
|
|
13662
|
+
function connectionCredentialReference(profile) {
|
|
13663
|
+
if (profile.auth.type === "env") return `env:${profile.auth.name}`;
|
|
13664
|
+
return "none";
|
|
13665
|
+
}
|
|
13666
|
+
function connectionEnvironmentTypos(environment = process.env) {
|
|
13667
|
+
return [
|
|
13668
|
+
{ name: "SEKIN_API", replacement: "SKEIN_API_KEY" },
|
|
13669
|
+
{ name: "SKEIN_BASEURL", replacement: "SKEIN_BASE_URL" }
|
|
13670
|
+
].filter(({ name }) => Object.hasOwn(environment, name));
|
|
13671
|
+
}
|
|
13672
|
+
function connectionIssues(profile, environment = process.env) {
|
|
13673
|
+
const issues = [];
|
|
13674
|
+
if (profile.provider !== "compatible") {
|
|
13675
|
+
issues.push("named primary connections support compatible relay providers only");
|
|
13676
|
+
}
|
|
13677
|
+
if (profile.provider === "compatible" && !profile.baseUrl) issues.push("compatible provider requires base URL");
|
|
13678
|
+
if (profile.provider === "compatible" && !relayProtocols.includes(profile.protocol)) {
|
|
13679
|
+
issues.push("relay transport must use openai-responses, openai-chat, or anthropic-messages");
|
|
13680
|
+
}
|
|
13681
|
+
if (profile.protocol === "anthropic-messages" && !profile.modelsBaseUrl) {
|
|
13682
|
+
issues.push("anthropic relay transport requires an explicit models base URL");
|
|
13683
|
+
}
|
|
13684
|
+
if (profile.auth.type === "env" && !environment[profile.auth.name]) {
|
|
13685
|
+
issues.push(`credential environment ${profile.auth.name} is not set`);
|
|
13686
|
+
}
|
|
13687
|
+
if (!profile.explicitAuth && profile.provider !== "compatible" && profile.baseUrl && !isOfficialProviderEndpoint(profile.provider, profile.baseUrl)) {
|
|
13688
|
+
issues.push("custom provider endpoint requires explicit connection auth");
|
|
13689
|
+
}
|
|
13690
|
+
return issues;
|
|
13691
|
+
}
|
|
13692
|
+
function normalizeProfile(id, connection, source, environment) {
|
|
13693
|
+
const explicitAuth = Boolean(connection.auth || connection.apiKeyEnv);
|
|
13694
|
+
const auth = connection.auth ?? (connection.apiKeyEnv ? { type: "env", name: connection.apiKeyEnv } : defaultAuth(connection.provider, connection.baseUrl, environment));
|
|
13695
|
+
return {
|
|
13696
|
+
id,
|
|
13697
|
+
...connection.label ? { label: connection.label } : {},
|
|
13698
|
+
provider: connection.provider,
|
|
13699
|
+
protocol: connection.protocol ?? defaultProtocol(connection.provider),
|
|
13700
|
+
...connection.baseUrl ? { baseUrl: connection.baseUrl } : {},
|
|
13701
|
+
...connection.modelsBaseUrl ? { modelsBaseUrl: connection.modelsBaseUrl } : {},
|
|
13702
|
+
...connection.defaultModel ? { defaultModel: connection.defaultModel } : {},
|
|
13703
|
+
auth,
|
|
13704
|
+
source,
|
|
13705
|
+
explicitAuth
|
|
13706
|
+
};
|
|
13707
|
+
}
|
|
13708
|
+
function defaultAuth(provider, baseUrl, environment) {
|
|
13709
|
+
if (provider === "compatible" && baseUrl && isLoopbackEndpoint2(baseUrl)) return { type: "none" };
|
|
13710
|
+
if (provider === "compatible") {
|
|
13711
|
+
return { type: "env", name: environment.SKEIN_API_KEY ? "SKEIN_API_KEY" : environment.MOSAIC_API_KEY ? "MOSAIC_API_KEY" : "SKEIN_API_KEY" };
|
|
13712
|
+
}
|
|
13713
|
+
return { type: "env", name: providerApiKeyEnv(provider) };
|
|
13714
|
+
}
|
|
13715
|
+
function defaultProtocol(provider) {
|
|
13716
|
+
if (provider === "anthropic") return "anthropic-messages";
|
|
13717
|
+
if (provider === "gemini") return "gemini";
|
|
13718
|
+
return "openai-chat";
|
|
13719
|
+
}
|
|
13720
|
+
function parseProviderField(value, name) {
|
|
13721
|
+
const provider = value?.trim().toLowerCase() || "compatible";
|
|
13722
|
+
if (provider !== "compatible") throw new Error(`${name} must be compatible for named relay connections.`);
|
|
13723
|
+
return "compatible";
|
|
13724
|
+
}
|
|
13725
|
+
function parseProtocolField(value, provider, name) {
|
|
13726
|
+
if (!value?.trim()) return provider === "compatible" ? "openai-responses" : defaultProtocol(provider);
|
|
13727
|
+
const protocol = value.trim().toLowerCase();
|
|
13728
|
+
if (!relayProtocols.includes(protocol)) throw new Error(`${name} must be openai-responses, openai-chat, or anthropic-messages.`);
|
|
13729
|
+
return protocol;
|
|
13730
|
+
}
|
|
13731
|
+
function optionalUrl(value, name) {
|
|
13732
|
+
if (!value?.trim()) return void 0;
|
|
13733
|
+
try {
|
|
13734
|
+
const url = new URL(value.trim());
|
|
13735
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("protocol");
|
|
13736
|
+
if (url.username || url.password || url.search || url.hash) throw new Error("unsafe URL components");
|
|
13737
|
+
return url.toString().replace(/\/$/u, "");
|
|
13738
|
+
} catch {
|
|
13739
|
+
throw new Error(`${name} must be an http or https URL.`);
|
|
13740
|
+
}
|
|
13741
|
+
}
|
|
13742
|
+
function optionalEnvironmentName(value, name) {
|
|
13743
|
+
if (!value?.trim()) return void 0;
|
|
13744
|
+
const normalized = value.trim();
|
|
13745
|
+
if (!/^[A-Z][A-Z0-9_]{0,127}$/u.test(normalized)) {
|
|
13746
|
+
throw new Error(`${name} must name an uppercase environment variable.`);
|
|
13747
|
+
}
|
|
13748
|
+
return normalized;
|
|
13749
|
+
}
|
|
13750
|
+
function isOfficialProviderEndpoint(provider, endpoint) {
|
|
13751
|
+
const official = {
|
|
13752
|
+
openai: "https://api.openai.com/v1",
|
|
13753
|
+
anthropic: "https://api.anthropic.com/v1",
|
|
13754
|
+
gemini: "https://generativelanguage.googleapis.com/v1beta"
|
|
13755
|
+
};
|
|
13756
|
+
return endpoint.replace(/\/+$/u, "") === official[provider];
|
|
13757
|
+
}
|
|
13758
|
+
function isLoopbackEndpoint2(endpoint) {
|
|
13759
|
+
try {
|
|
13760
|
+
const hostname = new URL(endpoint).hostname.replace(/^\[|\]$/gu, "").toLowerCase();
|
|
13761
|
+
return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "::1" || /^127(?:\.\d{1,3}){3}$/u.test(hostname);
|
|
13762
|
+
} catch {
|
|
13763
|
+
return false;
|
|
13764
|
+
}
|
|
13765
|
+
}
|
|
13766
|
+
function redactConnectionEndpoint(endpoint) {
|
|
13767
|
+
if (!endpoint) return "provider default";
|
|
13768
|
+
try {
|
|
13769
|
+
const url = new URL(endpoint);
|
|
13770
|
+
const authentication = url.username || url.password ? "<redacted>@" : "";
|
|
13771
|
+
return `${url.protocol}//${authentication}${url.host}${url.pathname}${url.search ? "?<redacted>" : ""}${url.hash ? "#<redacted>" : ""}`;
|
|
13772
|
+
} catch {
|
|
13773
|
+
return "configured endpoint";
|
|
13774
|
+
}
|
|
13775
|
+
}
|
|
13776
|
+
|
|
13195
13777
|
// src/agent/writer-lane.ts
|
|
13196
13778
|
import { createHash as createHash17 } from "node:crypto";
|
|
13197
13779
|
import { lstat as lstat19, mkdtemp, realpath as realpath7, rm as rm4 } from "node:fs/promises";
|
|
@@ -13750,7 +14332,7 @@ var DelegationManager = class {
|
|
|
13750
14332
|
maxReviewRounds: 0
|
|
13751
14333
|
});
|
|
13752
14334
|
await emit?.({ type: "team_start", id: board.id, objective: task });
|
|
13753
|
-
const writerId =
|
|
14335
|
+
const writerId = randomUUID18();
|
|
13754
14336
|
await emit?.({ type: "agent_queued", id: writerId, profile: profile.name, task, phase: "write" });
|
|
13755
14337
|
const draft = await this.writerLane.createDraft(
|
|
13756
14338
|
Math.min(this.team.maxWriterPatchBytes ?? 6e4, 12e4),
|
|
@@ -14120,7 +14702,7 @@ You are the only writer inside a disposable worktree. Make only the bounded requ
|
|
|
14120
14702
|
const reviewer = reviewerOverride ?? this.team.reviewerProfile ?? "reviewer";
|
|
14121
14703
|
const rounds = Math.max(0, this.team.maxReviewRounds ?? 1);
|
|
14122
14704
|
const board = await this.teamStore?.create({ objective, reviewer, maxReviewRounds: rounds });
|
|
14123
|
-
const runId = board?.id ??
|
|
14705
|
+
const runId = board?.id ?? randomUUID18();
|
|
14124
14706
|
await emit?.({ type: "team_start", id: runId, objective });
|
|
14125
14707
|
try {
|
|
14126
14708
|
let results = await this.runBatch(board?.id, tasks, "work", emit, signal);
|
|
@@ -14178,7 +14760,7 @@ ${review.summary}`,
|
|
|
14178
14760
|
}
|
|
14179
14761
|
}
|
|
14180
14762
|
async runBatch(runId, tasks, phase, emit, signal) {
|
|
14181
|
-
const scheduled = tasks.map((task) => ({ id:
|
|
14763
|
+
const scheduled = tasks.map((task) => ({ id: randomUUID18(), task }));
|
|
14182
14764
|
for (const item of scheduled) {
|
|
14183
14765
|
await emit?.({ type: "agent_queued", id: item.id, profile: item.task.profile, task: item.task.task, phase });
|
|
14184
14766
|
}
|
|
@@ -14287,12 +14869,12 @@ Start the response with exactly VERDICT: ACCEPT when the evidence is sufficient,
|
|
|
14287
14869
|
});
|
|
14288
14870
|
}
|
|
14289
14871
|
async peerMessage(runId, from, to, content, emit) {
|
|
14290
|
-
const id =
|
|
14872
|
+
const id = randomUUID18();
|
|
14291
14873
|
if (runId) await this.teamStore?.recordMessage(runId, { id, from, to, content });
|
|
14292
14874
|
await emit?.({ type: "agent_message", id, from, to, content });
|
|
14293
14875
|
}
|
|
14294
14876
|
async runOne(task, phase, emit, signal, retryOf, scheduledId) {
|
|
14295
|
-
const id = scheduledId ??
|
|
14877
|
+
const id = scheduledId ?? randomUUID18();
|
|
14296
14878
|
const profile = this.options.profiles.get(task.profile);
|
|
14297
14879
|
const configuredRoute = this.team.routes?.[task.profile];
|
|
14298
14880
|
const budgetMode = configuredRoute?.budgetMode ?? this.team.budgetMode ?? "observe";
|
|
@@ -14576,11 +15158,17 @@ function modelConfigFromRoute(route, parent, environment, connections) {
|
|
|
14576
15158
|
if (!provider) throw new Error("Agent route requires a provider or a valid connection.");
|
|
14577
15159
|
if (!route.model) throw new Error("Agent route requires a model or a team default model.");
|
|
14578
15160
|
const baseUrl = route.baseUrl ?? connection?.baseUrl;
|
|
14579
|
-
const
|
|
15161
|
+
const connectionAuth = connection?.auth;
|
|
15162
|
+
const apiKeyEnv = route.apiKeyEnv ?? connection?.apiKeyEnv ?? (connectionAuth?.type === "env" ? connectionAuth.name : void 0);
|
|
14580
15163
|
const inheritedKey = provider === parent.provider && baseUrl === parent.baseUrl ? parent.apiKey : void 0;
|
|
14581
|
-
|
|
15164
|
+
if (!apiKeyEnv && connectionAuth?.type !== "none" && !inheritedKey && provider !== "compatible" && baseUrl && !isOfficialProviderEndpoint(provider, baseUrl)) {
|
|
15165
|
+
throw new Error("Custom provider endpoints require explicit connection auth.");
|
|
15166
|
+
}
|
|
15167
|
+
const apiKey = connectionAuth?.type === "none" ? void 0 : apiKeyEnv ? environment[apiKeyEnv] : inheritedKey ?? defaultProviderApiKey(provider, environment);
|
|
15168
|
+
if (apiKeyEnv && !apiKey) throw new Error(`Agent connection credential environment ${apiKeyEnv} is not set.`);
|
|
14582
15169
|
return {
|
|
14583
15170
|
provider,
|
|
15171
|
+
...connection?.protocol ? { protocol: connection.protocol } : {},
|
|
14584
15172
|
model: route.model,
|
|
14585
15173
|
...baseUrl ? { baseUrl } : {},
|
|
14586
15174
|
...apiKey ? { apiKey } : {},
|
|
@@ -14674,22 +15262,44 @@ async function mapConcurrent(values, concurrency, operation) {
|
|
|
14674
15262
|
}
|
|
14675
15263
|
|
|
14676
15264
|
// src/agent/model-catalog.ts
|
|
15265
|
+
import { createHash as createHash18 } from "node:crypto";
|
|
15266
|
+
var MODEL_CATALOG_TTL_MS = 15 * 60 * 1e3;
|
|
15267
|
+
var MODEL_CATALOG_CACHE_LIMIT = 32;
|
|
15268
|
+
var modelCatalogCache = /* @__PURE__ */ new Map();
|
|
14677
15269
|
async function listConnectionModels(connection, environment = process.env) {
|
|
14678
15270
|
if (connection.provider !== "compatible" && connection.provider !== "openai") {
|
|
14679
15271
|
throw new Error(`Model discovery is currently supported for compatible and openai connections, not ${connection.provider}.`);
|
|
14680
15272
|
}
|
|
14681
|
-
|
|
14682
|
-
|
|
14683
|
-
|
|
15273
|
+
if (connection.protocol === "anthropic-messages" && !connection.modelsBaseUrl) {
|
|
15274
|
+
throw new Error("Anthropic relay model discovery requires an explicit modelsBaseUrl.");
|
|
15275
|
+
}
|
|
15276
|
+
const baseUrl = connection.modelsBaseUrl ?? connection.baseUrl ?? "https://api.openai.com/v1";
|
|
15277
|
+
const endpoint = baseUrl.endsWith("/models") ? baseUrl : `${baseUrl.replace(/\/+$/u, "")}/models`;
|
|
15278
|
+
const apiKey = connectionApiKey(connection, environment);
|
|
15279
|
+
const cacheKey = catalogFingerprint(endpoint, connection.auth?.type ?? (apiKey ? "legacy-env" : "none"), apiKey);
|
|
15280
|
+
const cached = modelCatalogCache.get(cacheKey);
|
|
15281
|
+
const now = Date.now();
|
|
15282
|
+
if (cached && cached.expiresAt > now) {
|
|
15283
|
+
touchCacheEntry(cacheKey, cached);
|
|
15284
|
+
return copyModels(cached.models);
|
|
15285
|
+
}
|
|
14684
15286
|
const response = await fetch(endpoint, {
|
|
15287
|
+
redirect: "error",
|
|
14685
15288
|
headers: {
|
|
14686
15289
|
accept: "application/json",
|
|
14687
|
-
...apiKey ? { authorization: `Bearer ${apiKey}` } : {}
|
|
15290
|
+
...apiKey ? { authorization: `Bearer ${apiKey}` } : {},
|
|
15291
|
+
...cached?.etag ? { "if-none-match": cached.etag } : {}
|
|
14688
15292
|
},
|
|
14689
15293
|
signal: AbortSignal.timeout(15e3)
|
|
14690
15294
|
});
|
|
15295
|
+
if (response.status === 304 && cached) {
|
|
15296
|
+
const refreshed = { ...cached, lastSuccessAt: now, expiresAt: now + MODEL_CATALOG_TTL_MS };
|
|
15297
|
+
touchCacheEntry(cacheKey, refreshed);
|
|
15298
|
+
return copyModels(refreshed.models);
|
|
15299
|
+
}
|
|
15300
|
+
if (response.status === 401 || response.status === 403) modelCatalogCache.delete(cacheKey);
|
|
14691
15301
|
const body = await response.text();
|
|
14692
|
-
if (!response.ok) throw new Error(`Model discovery failed (${response.status})
|
|
15302
|
+
if (!response.ok) throw new Error(`Model discovery failed (${response.status}).`);
|
|
14693
15303
|
if (body.length > 2e6) throw new Error("Model discovery response is too large.");
|
|
14694
15304
|
let parsed;
|
|
14695
15305
|
try {
|
|
@@ -14697,17 +15307,62 @@ async function listConnectionModels(connection, environment = process.env) {
|
|
|
14697
15307
|
} catch {
|
|
14698
15308
|
throw new Error("Model discovery returned invalid JSON.");
|
|
14699
15309
|
}
|
|
14700
|
-
const data = parsed && typeof parsed === "object" && Array.isArray(parsed.data) ? parsed.data : Array.isArray(parsed) ? parsed : [];
|
|
14701
|
-
|
|
14702
|
-
if (!value || typeof value !== "object"
|
|
15310
|
+
const data = parsed && typeof parsed === "object" && Array.isArray(parsed.data) ? parsed.data : parsed && typeof parsed === "object" && Array.isArray(parsed.models) ? parsed.models : Array.isArray(parsed) ? parsed : [];
|
|
15311
|
+
const models = data.flatMap((value) => {
|
|
15312
|
+
if (!value || typeof value !== "object") return [];
|
|
15313
|
+
const candidate = value;
|
|
15314
|
+
const id = typeof candidate.id === "string" ? candidate.id : typeof candidate.model_id === "string" ? candidate.model_id : void 0;
|
|
15315
|
+
if (!id) return [];
|
|
14703
15316
|
const item = value;
|
|
14704
15317
|
const contextLength = typeof item.context_length === "number" ? item.context_length : typeof item.contextLength === "number" ? item.contextLength : void 0;
|
|
14705
15318
|
return [{
|
|
14706
|
-
id
|
|
15319
|
+
id,
|
|
14707
15320
|
...typeof item.owned_by === "string" ? { ownedBy: item.owned_by } : typeof item.ownedBy === "string" ? { ownedBy: item.ownedBy } : {},
|
|
14708
15321
|
...contextLength !== void 0 ? { contextLength } : {}
|
|
14709
15322
|
}];
|
|
14710
15323
|
}).sort((left, right) => left.id.localeCompare(right.id));
|
|
15324
|
+
touchCacheEntry(cacheKey, {
|
|
15325
|
+
models,
|
|
15326
|
+
...response.headers.get("etag") ? { etag: response.headers.get("etag") } : {},
|
|
15327
|
+
lastSuccessAt: now,
|
|
15328
|
+
expiresAt: now + MODEL_CATALOG_TTL_MS
|
|
15329
|
+
});
|
|
15330
|
+
trimModelCatalogCache();
|
|
15331
|
+
return copyModels(models);
|
|
15332
|
+
}
|
|
15333
|
+
function catalogFingerprint(endpoint, authType, apiKey) {
|
|
15334
|
+
return createHash18("sha256").update(endpoint).update("\0").update(authType).update("\0").update(apiKey ?? "").digest("hex");
|
|
15335
|
+
}
|
|
15336
|
+
function touchCacheEntry(key, entry) {
|
|
15337
|
+
modelCatalogCache.delete(key);
|
|
15338
|
+
modelCatalogCache.set(key, entry);
|
|
15339
|
+
}
|
|
15340
|
+
function trimModelCatalogCache() {
|
|
15341
|
+
while (modelCatalogCache.size > MODEL_CATALOG_CACHE_LIMIT) {
|
|
15342
|
+
const oldest = modelCatalogCache.keys().next().value;
|
|
15343
|
+
if (!oldest) return;
|
|
15344
|
+
modelCatalogCache.delete(oldest);
|
|
15345
|
+
}
|
|
15346
|
+
}
|
|
15347
|
+
function copyModels(models) {
|
|
15348
|
+
return models.map((model) => ({ ...model }));
|
|
15349
|
+
}
|
|
15350
|
+
function connectionApiKey(connection, environment) {
|
|
15351
|
+
if (connection.auth?.type === "env") {
|
|
15352
|
+
const value = environment[connection.auth.name];
|
|
15353
|
+
if (!value) throw new Error(`Connection credential environment ${connection.auth.name} is not set.`);
|
|
15354
|
+
return value;
|
|
15355
|
+
}
|
|
15356
|
+
if (connection.auth?.type === "none") return void 0;
|
|
15357
|
+
if (connection.apiKeyEnv) {
|
|
15358
|
+
const value = environment[connection.apiKeyEnv];
|
|
15359
|
+
if (!value) throw new Error(`Connection credential environment ${connection.apiKeyEnv} is not set.`);
|
|
15360
|
+
return value;
|
|
15361
|
+
}
|
|
15362
|
+
if (connection.provider === "openai" && connection.baseUrl && connection.baseUrl.replace(/\/+$/u, "") !== "https://api.openai.com/v1") {
|
|
15363
|
+
throw new Error("Custom OpenAI model endpoints require explicit connection auth.");
|
|
15364
|
+
}
|
|
15365
|
+
return defaultConnectionApiKey(connection.provider, environment);
|
|
14711
15366
|
}
|
|
14712
15367
|
function defaultConnectionApiKey(provider, environment) {
|
|
14713
15368
|
if (provider === "openai") return environment.OPENAI_API_KEY;
|
|
@@ -14725,33 +15380,54 @@ function createAgentConnectionSetup(input2) {
|
|
|
14725
15380
|
if (!defaultModel || defaultModel.length > 256) {
|
|
14726
15381
|
throw new Error("Default model must contain between 1 and 256 characters.");
|
|
14727
15382
|
}
|
|
14728
|
-
|
|
14729
|
-
|
|
14730
|
-
throw new Error("OpenAI-compatible connections require a base URL.");
|
|
15383
|
+
if (input2.provider !== "compatible") {
|
|
15384
|
+
throw new Error("Named primary connections support third-party compatible relays only.");
|
|
14731
15385
|
}
|
|
14732
|
-
|
|
14733
|
-
|
|
15386
|
+
const protocol = input2.protocol ?? "openai-responses";
|
|
15387
|
+
if (protocol === "gemini") throw new Error("Relay protocol must use openai-responses, openai-chat, or anthropic-messages.");
|
|
15388
|
+
const baseUrl = input2.baseUrl?.trim() || void 0;
|
|
15389
|
+
if (!baseUrl) throw new Error("Compatible relay connections require an inference base URL.");
|
|
15390
|
+
const modelsBaseUrl = input2.modelsBaseUrl?.trim() || void 0;
|
|
15391
|
+
if (protocol === "anthropic-messages" && !modelsBaseUrl) {
|
|
15392
|
+
throw new Error("Anthropic relay connections require a separate models base URL.");
|
|
15393
|
+
}
|
|
15394
|
+
for (const [label, value] of [["Connection base URL", baseUrl], ["Models base URL", modelsBaseUrl]]) {
|
|
15395
|
+
if (!value) continue;
|
|
15396
|
+
let url;
|
|
14734
15397
|
try {
|
|
14735
|
-
|
|
15398
|
+
url = new URL(value);
|
|
14736
15399
|
} catch {
|
|
14737
|
-
throw new Error(
|
|
15400
|
+
throw new Error(`${label} must be a valid http or https URL.`);
|
|
14738
15401
|
}
|
|
14739
|
-
if (protocol !== "http:" && protocol !== "https:") {
|
|
14740
|
-
throw new Error(
|
|
15402
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
15403
|
+
throw new Error(`${label} must use http or https.`);
|
|
15404
|
+
}
|
|
15405
|
+
if (url.username || url.password || url.search || url.hash) {
|
|
15406
|
+
throw new Error(`${label} cannot contain credentials, query parameters, or fragments.`);
|
|
14741
15407
|
}
|
|
14742
15408
|
}
|
|
14743
15409
|
const apiKeyEnv = input2.apiKeyEnv?.trim() || void 0;
|
|
14744
15410
|
if (apiKeyEnv && !/^[A-Z][A-Z0-9_]{0,127}$/.test(apiKeyEnv)) {
|
|
14745
15411
|
throw new Error("Credential environment variable must use uppercase letters, numbers, and underscores.");
|
|
14746
15412
|
}
|
|
15413
|
+
const auth = input2.auth ?? (apiKeyEnv ? "env" : "none");
|
|
15414
|
+
if (auth === "env" && !apiKeyEnv) {
|
|
15415
|
+
throw new Error("Environment authentication requires a credential environment variable.");
|
|
15416
|
+
}
|
|
15417
|
+
if (auth === "none" && apiKeyEnv) {
|
|
15418
|
+
throw new Error("Unauthenticated connections cannot include a credential environment variable.");
|
|
15419
|
+
}
|
|
14747
15420
|
return {
|
|
14748
15421
|
defaultConnection: name,
|
|
14749
15422
|
defaultModel,
|
|
14750
15423
|
connections: {
|
|
14751
15424
|
[name]: {
|
|
14752
15425
|
provider: input2.provider,
|
|
14753
|
-
|
|
14754
|
-
|
|
15426
|
+
protocol,
|
|
15427
|
+
defaultModel,
|
|
15428
|
+
baseUrl,
|
|
15429
|
+
...modelsBaseUrl ? { modelsBaseUrl } : {},
|
|
15430
|
+
auth: auth === "env" ? { type: "env", name: apiKeyEnv } : { type: "none" }
|
|
14755
15431
|
}
|
|
14756
15432
|
}
|
|
14757
15433
|
};
|
|
@@ -14770,6 +15446,20 @@ import { constants as constants5 } from "node:fs";
|
|
|
14770
15446
|
import chalk from "chalk";
|
|
14771
15447
|
|
|
14772
15448
|
// src/ui/terminal-capabilities.ts
|
|
15449
|
+
function resolveTerminalAccessibility(environment = process.env) {
|
|
15450
|
+
const screenReader = truthy(environment.SKEIN_SCREEN_READER) || environment.INK_SCREEN_READER?.trim().toLowerCase() === "true";
|
|
15451
|
+
const dumb = environment.TERM?.trim().toLowerCase() === "dumb";
|
|
15452
|
+
const reducedMotion = screenReader || dumb || truthy(environment.SKEIN_REDUCE_MOTION);
|
|
15453
|
+
const ascii = screenReader || dumb || environment.SKEIN_GLYPHS === "ascii" || environment.MOSAIC_GLYPHS === "ascii";
|
|
15454
|
+
const color = !screenReader && !dumb && environment.NO_COLOR === void 0;
|
|
15455
|
+
return {
|
|
15456
|
+
screenReader,
|
|
15457
|
+
reducedMotion,
|
|
15458
|
+
ascii,
|
|
15459
|
+
color,
|
|
15460
|
+
incrementalRendering: !screenReader && !dumb
|
|
15461
|
+
};
|
|
15462
|
+
}
|
|
14773
15463
|
function resolveKittyKeyboardConfig(environment = process.env) {
|
|
14774
15464
|
const override = environment.SKEIN_KITTY_KEYBOARD?.trim().toLowerCase();
|
|
14775
15465
|
if (override && ["1", "true", "yes", "on", "enabled"].includes(override)) {
|
|
@@ -14791,6 +15481,9 @@ function enabledKittyKeyboard() {
|
|
|
14791
15481
|
function disabledKittyKeyboard() {
|
|
14792
15482
|
return { mode: "disabled", flags: ["disambiguateEscapeCodes"] };
|
|
14793
15483
|
}
|
|
15484
|
+
function truthy(value) {
|
|
15485
|
+
return value !== void 0 && ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
|
|
15486
|
+
}
|
|
14794
15487
|
|
|
14795
15488
|
// src/cli/glyphs.ts
|
|
14796
15489
|
var unicodeGlyphs = {
|
|
@@ -14922,6 +15615,22 @@ async function runDoctor(config, options = {}) {
|
|
|
14922
15615
|
required: false
|
|
14923
15616
|
});
|
|
14924
15617
|
}
|
|
15618
|
+
for (const typo of connectionEnvironmentTypos()) {
|
|
15619
|
+
checks.push({
|
|
15620
|
+
name: `Environment: ${typo.name}`,
|
|
15621
|
+
ok: false,
|
|
15622
|
+
detail: `unsupported spelling; use ${typo.replacement} (value was not read or printed)`,
|
|
15623
|
+
required: false
|
|
15624
|
+
});
|
|
15625
|
+
}
|
|
15626
|
+
for (const connection of config.connectionCatalog?.profiles ?? []) {
|
|
15627
|
+
checks.push({
|
|
15628
|
+
name: `Connection: ${connection.id}`,
|
|
15629
|
+
ok: connection.complete,
|
|
15630
|
+
detail: `${connection.source} ${connection.provider}/${connection.protocol}; ${connection.authType}/${connection.authStatus}; inference ${connection.endpoint}; models ${connection.modelsEndpoint}${connection.issues.length ? `; ${connection.issues.join("; ")}` : ""}`,
|
|
15631
|
+
required: false
|
|
15632
|
+
});
|
|
15633
|
+
}
|
|
14925
15634
|
const nodeOk = supportsNodeVersion(process.versions.node);
|
|
14926
15635
|
checks.push({
|
|
14927
15636
|
name: "Node.js",
|
|
@@ -15221,6 +15930,7 @@ var HeadlessReporter = class {
|
|
|
15221
15930
|
process.stdout.write(`${JSON.stringify({
|
|
15222
15931
|
type: "session",
|
|
15223
15932
|
...outcome2,
|
|
15933
|
+
...this.options.connection ? { connection: this.options.connection } : {},
|
|
15224
15934
|
session: sessionSummary(session)
|
|
15225
15935
|
})}
|
|
15226
15936
|
`);
|
|
@@ -15230,6 +15940,7 @@ var HeadlessReporter = class {
|
|
|
15230
15940
|
process.stdout.write(`${JSON.stringify({
|
|
15231
15941
|
type: "result",
|
|
15232
15942
|
...outcome2,
|
|
15943
|
+
...this.options.connection ? { connection: this.options.connection } : {},
|
|
15233
15944
|
response: this.finalResponse,
|
|
15234
15945
|
session: sessionSummary(session),
|
|
15235
15946
|
...completion ? { completion } : {},
|
|
@@ -15251,6 +15962,13 @@ var HeadlessReporter = class {
|
|
|
15251
15962
|
${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.separator} ${usage.toLocaleString()} tokens (${usageLabel}) ${this.glyphs.separator} session ${session.id.slice(0, 8)}
|
|
15252
15963
|
`
|
|
15253
15964
|
));
|
|
15965
|
+
if (this.options.connection) {
|
|
15966
|
+
const connection = this.options.connection;
|
|
15967
|
+
process.stderr.write(this.paint.dim(
|
|
15968
|
+
`${this.glyphs.meta} connection @${connection.id} ${this.glyphs.separator} ${connection.protocol} ${this.glyphs.separator} ${connection.source} ${this.glyphs.separator} ${connection.authType}/${connection.authStatus} ${this.glyphs.separator} inference ${connection.endpoint} ${this.glyphs.separator} models ${connection.modelsEndpoint}
|
|
15969
|
+
`
|
|
15970
|
+
));
|
|
15971
|
+
}
|
|
15254
15972
|
}
|
|
15255
15973
|
return outcome2;
|
|
15256
15974
|
}
|
|
@@ -15262,6 +15980,7 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
|
|
|
15262
15980
|
type: "final",
|
|
15263
15981
|
...outcome2,
|
|
15264
15982
|
error: message2,
|
|
15983
|
+
...this.options.connection ? { connection: this.options.connection } : {},
|
|
15265
15984
|
...session ? { session: sessionSummary(session) } : {}
|
|
15266
15985
|
})}
|
|
15267
15986
|
`);
|
|
@@ -15272,6 +15991,7 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
|
|
|
15272
15991
|
type: "result",
|
|
15273
15992
|
...outcome2,
|
|
15274
15993
|
error: message2,
|
|
15994
|
+
...this.options.connection ? { connection: this.options.connection } : {},
|
|
15275
15995
|
...session ? { session: sessionSummary(session) } : {}
|
|
15276
15996
|
}, null, 2)}
|
|
15277
15997
|
`);
|
|
@@ -16183,7 +16903,7 @@ function Header({ config, askMode, planMode = false, width = 80, glyphMode = "au
|
|
|
16183
16903
|
const brand = `${glyphs.brand} ${PRODUCT_NAME.toUpperCase()}`;
|
|
16184
16904
|
const modeLabel = `${glyphs.activity} ${mode}`;
|
|
16185
16905
|
const separator = ` ${glyphs.separator} `;
|
|
16186
|
-
const model = sanitizeInlineTerminalText(`${config.model.provider}/${config.model.model}`);
|
|
16906
|
+
const model = sanitizeInlineTerminalText(`${config.activeConnection && config.activeConnection.source !== "legacy" ? `@${config.activeConnection.id} ` : ""}${config.model.provider}/${config.model.model}`);
|
|
16187
16907
|
const repository = sanitizeInlineTerminalText(basename11(root) || root);
|
|
16188
16908
|
const minimum2 = `${brand} ${modeLabel}`;
|
|
16189
16909
|
const withRepository = `${brand}${separator}${repository}${separator}${modeLabel}`;
|
|
@@ -16219,7 +16939,7 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
|
|
|
16219
16939
|
if (!items.length) {
|
|
16220
16940
|
return /* @__PURE__ */ jsx(Box, { paddingLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: theme.muted, children: "Start with a request, @file, or /help." }) });
|
|
16221
16941
|
}
|
|
16222
|
-
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: items.map((item, index) => {
|
|
16942
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", "aria-role": "list", children: items.map((item, index) => {
|
|
16223
16943
|
if (item.kind === "user") {
|
|
16224
16944
|
return /* @__PURE__ */ jsxs(Box, { marginBottom: compact3 || item.clipped ? 0 : 1, children: [
|
|
16225
16945
|
/* @__PURE__ */ jsx(Box, { width: 2, children: /* @__PURE__ */ jsx(Text, { bold: true, color: theme.accent, children: glyphs.prompt }) }),
|
|
@@ -16690,7 +17410,7 @@ function PermissionCard({ call, category, reason, width = 80, glyphMode = "auto"
|
|
|
16690
17410
|
{ text: "[Esc]", color: theme.muted }
|
|
16691
17411
|
];
|
|
16692
17412
|
const marker = glyphs.borderStyle === "classic" ? "!" : "\u258E";
|
|
16693
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
17413
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, "aria-role": "radiogroup", children: [
|
|
16694
17414
|
/* @__PURE__ */ jsx(PermissionLine, { marker, children: /* @__PURE__ */ jsx(Text, { bold: true, color: theme.warning, children: title }) }),
|
|
16695
17415
|
/* @__PURE__ */ jsx(PermissionLine, { marker, children: /* @__PURE__ */ jsx(Text, { color: theme.muted, children: tool }) }),
|
|
16696
17416
|
/* @__PURE__ */ jsx(PermissionLine, { marker, children: /* @__PURE__ */ jsx(Text, { color: theme.text, children: summaryLine }) }),
|
|
@@ -16753,7 +17473,7 @@ function PromptBar({ busy, value, placeholder, width = 80, mode = "chat", queueC
|
|
|
16753
17473
|
] }),
|
|
16754
17474
|
/* @__PURE__ */ jsx(Text, { color: theme.text, children: truncateDisplay(safeQueuePreview, queuePreviewWidth) })
|
|
16755
17475
|
] }) : null,
|
|
16756
|
-
/* @__PURE__ */ jsxs(Box, { children: [
|
|
17476
|
+
/* @__PURE__ */ jsxs(Box, { "aria-role": "textbox", children: [
|
|
16757
17477
|
/* @__PURE__ */ jsx(Text, { bold: true, color: shell ? theme.warning : theme.accent, children: shell ? "! " : `${glyphs.prompt} ` }),
|
|
16758
17478
|
children
|
|
16759
17479
|
] }),
|
|
@@ -18417,13 +19137,14 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18417
19137
|
const terminalHeight = Math.max(1, rows || 24);
|
|
18418
19138
|
const horizontalPadding = terminalWidth >= 24 ? 1 : 0;
|
|
18419
19139
|
const contentWidth = Math.max(1, terminalWidth - horizontalPadding * 2);
|
|
18420
|
-
const
|
|
19140
|
+
const terminalAccessibility = resolveTerminalAccessibility();
|
|
19141
|
+
const glyphMode = terminalAccessibility.ascii ? "ascii" : "auto";
|
|
18421
19142
|
const glyphs = resolveGlyphs(glyphMode);
|
|
18422
19143
|
const separator = ` ${glyphs.separator} `;
|
|
18423
19144
|
const ellipsis = terminalEllipsis();
|
|
18424
19145
|
const initialSession = runner.getSession();
|
|
18425
19146
|
const setupProblem = config.model.provider !== "compatible" && !config.model.apiKey ? `No ${config.model.provider} API key found. Set it and restart: export ${providerApiKeyEnv(config.model.provider)}=<your-key>${separator}then run ${PRODUCT_COMMAND} again. Use ${PRODUCT_COMMAND} doctor to verify, or --model to switch provider.` : config.model.provider === "compatible" && !config.model.baseUrl ? `No model endpoint configured. Set one and restart: export SKEIN_BASE_URL=<endpoint>${separator}or pass --base-url <endpoint>. Run ${PRODUCT_COMMAND} doctor to verify.` : void 0;
|
|
18426
|
-
const colorEnabled = config.ui.color &&
|
|
19147
|
+
const colorEnabled = config.ui.color && terminalAccessibility.color;
|
|
18427
19148
|
const [theme, setTheme] = useState2(() => resolveThemeWithColor(config.ui.theme, colorEnabled));
|
|
18428
19149
|
const [themeCatalogRevision, setThemeCatalogRevision] = useState2(0);
|
|
18429
19150
|
const [compact3, setCompact] = useState2(config.ui.compact);
|
|
@@ -18559,13 +19280,13 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18559
19280
|
setHistorySearch((current) => current ? setHistorySearchQuery(current, input2) : current);
|
|
18560
19281
|
}, [input2]);
|
|
18561
19282
|
useEffect2(() => {
|
|
18562
|
-
if (!busy || reducedMotion
|
|
19283
|
+
if (!busy || terminalAccessibility.reducedMotion) {
|
|
18563
19284
|
setFrameIndex(0);
|
|
18564
19285
|
return void 0;
|
|
18565
19286
|
}
|
|
18566
19287
|
const timer = setInterval(() => setFrameIndex((value) => (value + 1) % spinnerFrames().length), 120);
|
|
18567
19288
|
return () => clearInterval(timer);
|
|
18568
|
-
}, [busy]);
|
|
19289
|
+
}, [busy, terminalAccessibility.reducedMotion]);
|
|
18569
19290
|
const requestPermission = useCallback((call, category, reason) => {
|
|
18570
19291
|
return new Promise((resolve28) => setPermission({
|
|
18571
19292
|
call,
|
|
@@ -19226,10 +19947,19 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
19226
19947
|
return true;
|
|
19227
19948
|
}
|
|
19228
19949
|
const routes = Object.values(config.agents?.routes ?? {});
|
|
19229
|
-
const connections =
|
|
19230
|
-
appendList("Model connections", connections.length ? connections.map((
|
|
19231
|
-
label: `${
|
|
19232
|
-
detail:
|
|
19950
|
+
const connections = config.connectionCatalog?.profiles ?? [];
|
|
19951
|
+
appendList("Model connections", connections.length ? connections.map((connection) => ({
|
|
19952
|
+
label: `${connection.id} ${connection.provider} ${connection.source}`,
|
|
19953
|
+
detail: [
|
|
19954
|
+
...config.activeConnection?.id === connection.id ? ["active"] : [],
|
|
19955
|
+
...config.connectionCatalog?.defaultConnection === connection.id ? ["default"] : [],
|
|
19956
|
+
`${connection.authType}/${connection.authStatus}`,
|
|
19957
|
+
connection.protocol,
|
|
19958
|
+
`inference ${connection.endpoint}`,
|
|
19959
|
+
`models ${connection.modelsEndpoint}`,
|
|
19960
|
+
`${routes.filter((route) => route.connection === connection.id).length} explicit routes`
|
|
19961
|
+
].join(separator),
|
|
19962
|
+
tone: connection.complete ? "success" : "warning"
|
|
19233
19963
|
})) : [{ label: "No named model connections configured.", detail: `Run ${PRODUCT_COMMAND} agents setup before starting another session.` }]);
|
|
19234
19964
|
return true;
|
|
19235
19965
|
}
|
|
@@ -19866,7 +20596,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
19866
20596
|
const mcpServers = extensions?.mcpStatus() ?? [];
|
|
19867
20597
|
const memoryStats = extensions?.memoryStats();
|
|
19868
20598
|
const workspacePanelStatus = workspaceReadiness ? {
|
|
19869
|
-
model: `${config.model.provider}/${config.model.model}`,
|
|
20599
|
+
model: `${config.activeConnection && config.activeConnection.source !== "legacy" ? `@${config.activeConnection.id} ` : ""}${config.model.provider}/${config.model.model}`,
|
|
19870
20600
|
mode: interactionMode,
|
|
19871
20601
|
context: workspaceReadiness.files ? "ready" : "empty",
|
|
19872
20602
|
files: workspaceReadiness.files,
|
|
@@ -20004,14 +20734,17 @@ function permissionPosture(config) {
|
|
|
20004
20734
|
}
|
|
20005
20735
|
async function runInteractiveTui(options) {
|
|
20006
20736
|
await reloadUserThemes();
|
|
20737
|
+
const terminalAccessibility = resolveTerminalAccessibility();
|
|
20007
20738
|
const instance = render2(/* @__PURE__ */ jsx3(SkeinApp, { ...options }), {
|
|
20008
20739
|
exitOnCtrlC: false,
|
|
20009
20740
|
patchConsole: true,
|
|
20010
|
-
incrementalRendering:
|
|
20741
|
+
incrementalRendering: terminalAccessibility.incrementalRendering,
|
|
20742
|
+
isScreenReaderEnabled: terminalAccessibility.screenReader,
|
|
20011
20743
|
maxFps: 30,
|
|
20012
20744
|
kittyKeyboard: resolveKittyKeyboardConfig()
|
|
20013
20745
|
});
|
|
20014
20746
|
await instance.waitUntilExit();
|
|
20747
|
+
if (terminalAccessibility.screenReader) process.stdout.write("\n");
|
|
20015
20748
|
}
|
|
20016
20749
|
function initialTimeline(session, banner, setupProblem) {
|
|
20017
20750
|
const items = session.messages.filter((message2) => (message2.role === "user" || message2.role === "assistant") && visibleMessage(message2)).slice(-20).map((message2) => ({ id: message2.id, kind: message2.role, text: message2.content }));
|
|
@@ -20201,12 +20934,9 @@ function toolDetail2(call) {
|
|
|
20201
20934
|
return keys.slice(0, 3).join(", ");
|
|
20202
20935
|
}
|
|
20203
20936
|
function spinnerFrames() {
|
|
20204
|
-
const ascii =
|
|
20937
|
+
const ascii = resolveTerminalAccessibility().ascii;
|
|
20205
20938
|
return ascii ? [".", "o", "O", "o"] : ["\u25CC", "\u25CD", "\u25CE", "\u25C9", "\u25CE", "\u25CD"];
|
|
20206
20939
|
}
|
|
20207
|
-
function reducedMotion() {
|
|
20208
|
-
return process.env.SKEIN_REDUCE_MOTION === "1" || process.env.SKEIN_REDUCE_MOTION === "true" || process.env.INK_SCREEN_READER === "true" || process.env.TERM === "dumb";
|
|
20209
|
-
}
|
|
20210
20940
|
|
|
20211
20941
|
// src/ui/workspace-preparation.tsx
|
|
20212
20942
|
import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
|
|
@@ -20229,10 +20959,11 @@ function WorkspacePreparationView({
|
|
|
20229
20959
|
}) {
|
|
20230
20960
|
const theme = useTheme();
|
|
20231
20961
|
const safeWidth2 = Math.max(1, Math.floor(width));
|
|
20232
|
-
const innerWidth = Math.max(1, safeWidth2 - (safeWidth2 >= 24 ? 4 : 0));
|
|
20233
20962
|
const compact3 = safeWidth2 < 48;
|
|
20234
20963
|
const constrained = height < 14;
|
|
20235
|
-
const
|
|
20964
|
+
const horizontalPadding = safeWidth2 >= 24 && !constrained ? 2 : 0;
|
|
20965
|
+
const innerWidth = Math.max(1, safeWidth2 - horizontalPadding * 2);
|
|
20966
|
+
const ascii = resolveTerminalAccessibility().ascii;
|
|
20236
20967
|
const spinner = (ascii ? [".", "o", "O", "o"] : ["\u25DC", "\u25E0", "\u25DD", "\u25DE", "\u25E1", "\u25DF"])[frame % (ascii ? 4 : 6)];
|
|
20237
20968
|
const separator = ascii ? "|" : "\xB7";
|
|
20238
20969
|
const brand = ascii ? "*" : PRODUCT_MARK;
|
|
@@ -20242,7 +20973,7 @@ function WorkspacePreparationView({
|
|
|
20242
20973
|
const modelLine = `model ${sanitizeTerminalText(model)}`;
|
|
20243
20974
|
const workspaceLine = `workspace ${compactDisplayPath(sanitizeTerminalText(workspace), Math.max(1, innerWidth - 10))}`;
|
|
20244
20975
|
const steps = preparationSteps(phase, progress, readiness, error, { ascii, spinner });
|
|
20245
|
-
return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", paddingX:
|
|
20976
|
+
return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", paddingX: horizontalPadding, children: [
|
|
20246
20977
|
/* @__PURE__ */ jsxs4(Box3, { children: [
|
|
20247
20978
|
/* @__PURE__ */ jsxs4(Text4, { bold: true, color: theme.accent, children: [
|
|
20248
20979
|
brand,
|
|
@@ -20260,11 +20991,11 @@ function WorkspacePreparationView({
|
|
|
20260
20991
|
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: truncateDisplay(modelLine, innerWidth) }),
|
|
20261
20992
|
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: truncateDisplay(workspaceLine, innerWidth) })
|
|
20262
20993
|
] }) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: truncateDisplay(workspaceLine, innerWidth) }),
|
|
20263
|
-
/* @__PURE__ */ jsx4(Box3, { marginTop: 1, flexDirection: constrained ? "row" : "column", children: steps.map((step2) => constrained ? /* @__PURE__ */ jsxs4(Text4, { color: step2.state === "active" ? theme.accent : step2.state === "complete" ? theme.success : step2.state === "error" ? theme.error : theme.dim, children: [
|
|
20994
|
+
/* @__PURE__ */ jsx4(Box3, { marginTop: 1, flexDirection: constrained ? "row" : "column", children: steps.map((step2, index) => constrained ? /* @__PURE__ */ jsxs4(Text4, { color: step2.state === "active" ? theme.accent : step2.state === "complete" ? theme.success : step2.state === "error" ? theme.error : theme.dim, children: [
|
|
20995
|
+
index ? " " : "",
|
|
20264
20996
|
step2.glyph,
|
|
20265
20997
|
" ",
|
|
20266
|
-
step2.id === "persist" ? "save" : step2.id
|
|
20267
|
-
step2.id === "verify" ? "" : " "
|
|
20998
|
+
step2.id === "persist" ? "save" : step2.id
|
|
20268
20999
|
] }, step2.id) : /* @__PURE__ */ jsxs4(Box3, { height: 1, overflowY: "hidden", children: [
|
|
20269
21000
|
/* @__PURE__ */ jsxs4(Text4, { bold: true, color: step2.state === "active" ? theme.accent : step2.state === "complete" ? theme.success : step2.state === "error" ? theme.error : theme.border, children: [
|
|
20270
21001
|
step2.glyph,
|
|
@@ -20276,7 +21007,7 @@ function WorkspacePreparationView({
|
|
|
20276
21007
|
truncateDisplay(step2.detail, Math.max(1, innerWidth - displayWidth(step2.glyph) - (compact3 ? 12 : 14)))
|
|
20277
21008
|
] })
|
|
20278
21009
|
] }, step2.id)) }),
|
|
20279
|
-
/* @__PURE__ */ jsxs4(Box3, { marginTop: 1, children: [
|
|
21010
|
+
/* @__PURE__ */ jsxs4(Box3, { marginTop: constrained ? 0 : 1, children: [
|
|
20280
21011
|
/* @__PURE__ */ jsxs4(Text4, { bold: true, color: error ? theme.error : readiness ? theme.success : theme.accent, children: [
|
|
20281
21012
|
readiness ? ascii ? "[ok]" : "\u2713" : error ? ascii ? "[x]" : "\xD7" : spinner,
|
|
20282
21013
|
" "
|
|
@@ -20385,7 +21116,8 @@ function WorkspacePreparationApp({
|
|
|
20385
21116
|
}
|
|
20386
21117
|
async function runWorkspacePreparation(engine, config, options = {}) {
|
|
20387
21118
|
let result;
|
|
20388
|
-
const
|
|
21119
|
+
const terminalAccessibility = resolveTerminalAccessibility();
|
|
21120
|
+
const colorEnabled = config.ui.color && terminalAccessibility.color;
|
|
20389
21121
|
const theme = resolveThemeWithColor(config.ui.theme, colorEnabled);
|
|
20390
21122
|
const instance = render3(
|
|
20391
21123
|
/* @__PURE__ */ jsx4(ThemeProvider, { theme, children: /* @__PURE__ */ jsx4(
|
|
@@ -20407,11 +21139,13 @@ async function runWorkspacePreparation(engine, config, options = {}) {
|
|
|
20407
21139
|
...options.stderr ? { stderr: options.stderr } : {},
|
|
20408
21140
|
exitOnCtrlC: false,
|
|
20409
21141
|
patchConsole: false,
|
|
20410
|
-
incrementalRendering:
|
|
21142
|
+
incrementalRendering: terminalAccessibility.incrementalRendering,
|
|
21143
|
+
isScreenReaderEnabled: terminalAccessibility.screenReader,
|
|
20411
21144
|
kittyKeyboard: resolveKittyKeyboardConfig()
|
|
20412
21145
|
}
|
|
20413
21146
|
);
|
|
20414
21147
|
await instance.waitUntilExit();
|
|
21148
|
+
if (terminalAccessibility.screenReader) (options.stdout ?? process.stdout).write("\n");
|
|
20415
21149
|
return result ?? { status: "cancelled" };
|
|
20416
21150
|
}
|
|
20417
21151
|
function preparationLabel(phase, progress, readiness, compact3 = false) {
|
|
@@ -20538,19 +21272,16 @@ var build_default = TextInput;
|
|
|
20538
21272
|
|
|
20539
21273
|
// src/ui/onboarding.tsx
|
|
20540
21274
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
20541
|
-
var
|
|
20542
|
-
{
|
|
20543
|
-
{
|
|
20544
|
-
{
|
|
20545
|
-
];
|
|
20546
|
-
var methods = [
|
|
20547
|
-
{ value: "official", label: "Provider API key", detail: "OpenAI, Anthropic, or Gemini \xB7 direct billing" },
|
|
20548
|
-
{ value: "relay", label: "Compatible endpoint", detail: "Local server or relay \xB7 protocol chosen explicitly" }
|
|
21275
|
+
var relayProtocols2 = [
|
|
21276
|
+
{ value: "openai-responses", label: "OpenAI Responses", detail: "Recommended \xB7 /responses \xB7 stateless history replay" },
|
|
21277
|
+
{ value: "openai-chat", label: "OpenAI Chat Completions", detail: "Compatibility \xB7 /chat/completions" },
|
|
21278
|
+
{ value: "anthropic-messages", label: "Anthropic Messages", detail: "Compatibility \xB7 Anthropic SDK-style base URL" }
|
|
20549
21279
|
];
|
|
20550
|
-
var
|
|
20551
|
-
{ value: "
|
|
20552
|
-
{ value: "
|
|
21280
|
+
var authMethods = [
|
|
21281
|
+
{ value: "env", label: "Environment variable", detail: "Recommended \xB7 only the variable name is saved" },
|
|
21282
|
+
{ value: "none", label: "No authentication", detail: "For trusted keyless relays or local servers" }
|
|
20553
21283
|
];
|
|
21284
|
+
var DEFAULT_CONNECTION_NAME = "primary-relay";
|
|
20554
21285
|
var forbiddenDirectionControls = /[\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/u;
|
|
20555
21286
|
var directionControls = /[\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/gu;
|
|
20556
21287
|
var inputControl = /[\u0000-\u001f\u007f-\u009f]/u;
|
|
@@ -20560,18 +21291,16 @@ function needsFirstRunOnboarding(config) {
|
|
|
20560
21291
|
}
|
|
20561
21292
|
function createOnboardingState(config) {
|
|
20562
21293
|
return {
|
|
20563
|
-
step: "
|
|
21294
|
+
step: "relay-protocol",
|
|
20564
21295
|
history: [],
|
|
20565
21296
|
selected: 0,
|
|
20566
21297
|
draft: {
|
|
20567
|
-
method: void 0,
|
|
20568
|
-
provider: void 0,
|
|
20569
21298
|
relayProtocol: void 0,
|
|
20570
21299
|
baseUrl: config.model.baseUrl ?? "",
|
|
20571
|
-
|
|
20572
|
-
|
|
20573
|
-
|
|
20574
|
-
|
|
21300
|
+
modelsBaseUrl: "",
|
|
21301
|
+
model: config.model.provider === "compatible" ? config.model.model : "default",
|
|
21302
|
+
auth: void 0,
|
|
21303
|
+
apiKeyEnv: "SKEIN_API_KEY"
|
|
20575
21304
|
},
|
|
20576
21305
|
error: void 0
|
|
20577
21306
|
};
|
|
@@ -20634,78 +21363,58 @@ function validateRelayBaseUrl(value) {
|
|
|
20634
21363
|
return { ok: false, error: "Remote relays must use HTTPS; HTTP is allowed only for loopback." };
|
|
20635
21364
|
}
|
|
20636
21365
|
const path = url.pathname.replace(/\/+$/u, "").toLocaleLowerCase();
|
|
20637
|
-
if (path.endsWith("/chat/completions") || path.endsWith("/messages")) {
|
|
20638
|
-
return { ok: false, error: "Enter the API base URL, not
|
|
21366
|
+
if (path.endsWith("/responses") || path.endsWith("/chat/completions") || path.endsWith("/messages")) {
|
|
21367
|
+
return { ok: false, error: "Enter the API base URL, not a final inference endpoint." };
|
|
20639
21368
|
}
|
|
20640
21369
|
url.pathname = url.pathname.replace(/\/+$/u, "");
|
|
20641
21370
|
const normalized = url.toString().replace(/\/+$/u, "");
|
|
20642
21371
|
return { ok: true, value: normalized, loopback };
|
|
20643
21372
|
}
|
|
20644
21373
|
function buildOnboardingConfig(state) {
|
|
20645
|
-
const
|
|
21374
|
+
const protocol = state.draft.relayProtocol;
|
|
20646
21375
|
const model = validateModel(state.draft.model);
|
|
20647
|
-
|
|
20648
|
-
const
|
|
20649
|
-
|
|
20650
|
-
|
|
20651
|
-
|
|
20652
|
-
|
|
20653
|
-
return {
|
|
20654
|
-
model: {
|
|
20655
|
-
provider,
|
|
20656
|
-
model: model.value,
|
|
20657
|
-
baseUrl: endpoint.value,
|
|
20658
|
-
...apiKey.value ? { apiKey: apiKey.value } : {}
|
|
20659
|
-
}
|
|
20660
|
-
};
|
|
21376
|
+
const endpoint = validateRelayBaseUrl(state.draft.baseUrl);
|
|
21377
|
+
const modelsEndpoint = validateModelsBaseUrl(state.draft.modelsBaseUrl, protocol === "anthropic-messages");
|
|
21378
|
+
const auth = state.draft.auth;
|
|
21379
|
+
const apiKeyEnv = validateEnvironmentName(state.draft.apiKeyEnv, auth === "env");
|
|
21380
|
+
if (!protocol || !model.ok || !endpoint.ok || !modelsEndpoint.ok || !auth || !apiKeyEnv.ok) {
|
|
21381
|
+
throw new Error("Onboarding relay configuration is incomplete.");
|
|
20661
21382
|
}
|
|
20662
|
-
|
|
21383
|
+
const setup = createAgentConnectionSetup({
|
|
21384
|
+
name: DEFAULT_CONNECTION_NAME,
|
|
21385
|
+
provider: "compatible",
|
|
21386
|
+
protocol,
|
|
21387
|
+
baseUrl: endpoint.value,
|
|
21388
|
+
...modelsEndpoint.value ? { modelsBaseUrl: modelsEndpoint.value } : {},
|
|
21389
|
+
auth,
|
|
21390
|
+
...apiKeyEnv.value ? { apiKeyEnv: apiKeyEnv.value } : {},
|
|
21391
|
+
defaultModel: model.value
|
|
21392
|
+
});
|
|
21393
|
+
return { agents: mergeAgentSetup(void 0, setup) };
|
|
20663
21394
|
}
|
|
20664
21395
|
function selectCurrentOption(state) {
|
|
20665
|
-
if (state.step === "method") {
|
|
20666
|
-
const method = methods[state.selected]?.value;
|
|
20667
|
-
if (!method) return state;
|
|
20668
|
-
if (method === "official") {
|
|
20669
|
-
return advance({ ...state, draft: { ...state.draft, method, relayProtocol: void 0 } }, "official-provider");
|
|
20670
|
-
}
|
|
20671
|
-
if (method === "relay") {
|
|
20672
|
-
return advance({ ...state, draft: { ...state.draft, method } }, "relay-protocol");
|
|
20673
|
-
}
|
|
20674
|
-
return state;
|
|
20675
|
-
}
|
|
20676
|
-
if (state.step === "official-provider") {
|
|
20677
|
-
const provider = officialProviders[state.selected]?.provider;
|
|
20678
|
-
if (!provider) return state;
|
|
20679
|
-
return advance({
|
|
20680
|
-
...state,
|
|
20681
|
-
draft: {
|
|
20682
|
-
...state.draft,
|
|
20683
|
-
method: "official",
|
|
20684
|
-
provider,
|
|
20685
|
-
relayProtocol: void 0,
|
|
20686
|
-
baseUrl: "",
|
|
20687
|
-
model: defaultModelForProvider(provider),
|
|
20688
|
-
apiKey: ""
|
|
20689
|
-
}
|
|
20690
|
-
}, "model");
|
|
20691
|
-
}
|
|
20692
21396
|
if (state.step === "relay-protocol") {
|
|
20693
|
-
const relayProtocol =
|
|
21397
|
+
const relayProtocol = relayProtocols2[state.selected]?.value;
|
|
20694
21398
|
if (!relayProtocol) return state;
|
|
20695
|
-
const provider = relayProtocol === "openai-compatible" ? "compatible" : "anthropic";
|
|
20696
21399
|
return advance({
|
|
20697
21400
|
...state,
|
|
20698
21401
|
draft: {
|
|
20699
21402
|
...state.draft,
|
|
20700
|
-
method: "relay",
|
|
20701
|
-
provider,
|
|
20702
21403
|
relayProtocol,
|
|
20703
21404
|
baseUrl: "",
|
|
20704
|
-
|
|
20705
|
-
|
|
21405
|
+
modelsBaseUrl: "",
|
|
21406
|
+
model: "default",
|
|
21407
|
+
auth: void 0,
|
|
21408
|
+
apiKeyEnv: "SKEIN_API_KEY"
|
|
20706
21409
|
}
|
|
20707
21410
|
}, "endpoint");
|
|
20708
21411
|
}
|
|
21412
|
+
if (state.step === "auth") {
|
|
21413
|
+
const auth = authMethods[state.selected]?.value;
|
|
21414
|
+
if (!auth) return state;
|
|
21415
|
+
const next = { ...state, draft: { ...state.draft, auth, apiKeyEnv: auth === "env" ? state.draft.apiKeyEnv : "" } };
|
|
21416
|
+
return advance(next, auth === "env" ? "api-key-env" : "confirm");
|
|
21417
|
+
}
|
|
20709
21418
|
return state;
|
|
20710
21419
|
}
|
|
20711
21420
|
function submitInput(state, field, rawValue) {
|
|
@@ -20714,22 +21423,30 @@ function submitInput(state, field, rawValue) {
|
|
|
20714
21423
|
if (field === "baseUrl") {
|
|
20715
21424
|
const endpoint = validateRelayBaseUrl(value);
|
|
20716
21425
|
if (!endpoint.ok) return { ...next, error: endpoint.error };
|
|
20717
|
-
return advance({ ...next, draft: { ...next.draft, baseUrl: endpoint.value } }, "
|
|
21426
|
+
return advance({ ...next, draft: { ...next.draft, baseUrl: endpoint.value } }, "models-endpoint");
|
|
21427
|
+
}
|
|
21428
|
+
if (field === "modelsBaseUrl") {
|
|
21429
|
+
const endpoint = validateModelsBaseUrl(value, state.draft.relayProtocol === "anthropic-messages");
|
|
21430
|
+
if (!endpoint.ok) return { ...next, error: endpoint.error };
|
|
21431
|
+
return advance({ ...next, draft: { ...next.draft, modelsBaseUrl: endpoint.value } }, "model");
|
|
20718
21432
|
}
|
|
20719
21433
|
if (field === "model") {
|
|
20720
21434
|
const model = validateModel(value);
|
|
20721
21435
|
if (!model.ok) return { ...next, error: model.error };
|
|
20722
|
-
return advance({ ...next, draft: { ...next.draft, model: model.value } }, "
|
|
21436
|
+
return advance({ ...next, draft: { ...next.draft, model: model.value } }, "auth");
|
|
20723
21437
|
}
|
|
20724
|
-
const
|
|
20725
|
-
if (!
|
|
20726
|
-
|
|
21438
|
+
const apiKeyEnv = validateEnvironmentName(value, true);
|
|
21439
|
+
if (!apiKeyEnv.ok) return { ...next, error: apiKeyEnv.error };
|
|
21440
|
+
if (!process.env[apiKeyEnv.value]) {
|
|
21441
|
+
return { ...next, error: `Environment variable ${apiKeyEnv.value} is not set. Export it, then restart Skein.` };
|
|
21442
|
+
}
|
|
21443
|
+
return advance({ ...next, draft: { ...next.draft, apiKeyEnv: apiKeyEnv.value } }, "confirm");
|
|
20727
21444
|
}
|
|
20728
21445
|
function advance(state, step2) {
|
|
20729
21446
|
return { ...state, step: step2, history: [...state.history, state.step], selected: 0, error: void 0 };
|
|
20730
21447
|
}
|
|
20731
21448
|
function sanitizeFieldInput(field, value) {
|
|
20732
|
-
const max = field === "baseUrl" ? 2048 : field === "model" ? 256 :
|
|
21449
|
+
const max = field === "baseUrl" || field === "modelsBaseUrl" ? 2048 : field === "model" ? 256 : 128;
|
|
20733
21450
|
return sanitizeTerminalText(value).replace(directionControls, "").replace(/\r?\n/gu, "").slice(0, max);
|
|
20734
21451
|
}
|
|
20735
21452
|
function validateModel(value) {
|
|
@@ -20740,25 +21457,25 @@ function validateModel(value) {
|
|
|
20740
21457
|
}
|
|
20741
21458
|
return { ok: true, value: model };
|
|
20742
21459
|
}
|
|
20743
|
-
function
|
|
20744
|
-
|
|
20745
|
-
if (!
|
|
20746
|
-
|
|
20747
|
-
return { ok: false, error: "The API key contains unsupported whitespace or control characters." };
|
|
20748
|
-
}
|
|
20749
|
-
return { ok: true, value: apiKey };
|
|
21460
|
+
function validateModelsBaseUrl(value, required) {
|
|
21461
|
+
if (!value.trim() && !required) return { ok: true, value: "" };
|
|
21462
|
+
if (!value.trim()) return { ok: false, error: "Anthropic transport requires an OpenAI-style models base URL." };
|
|
21463
|
+
return validateRelayBaseUrl(value);
|
|
20750
21464
|
}
|
|
20751
|
-
function
|
|
20752
|
-
|
|
20753
|
-
|
|
20754
|
-
|
|
21465
|
+
function validateEnvironmentName(value, required) {
|
|
21466
|
+
const name = value.trim();
|
|
21467
|
+
if (!name && !required) return { ok: true, value: "" };
|
|
21468
|
+
if (!/^[A-Z][A-Z0-9_]{0,127}$/u.test(name)) {
|
|
21469
|
+
return { ok: false, error: "Use an uppercase environment variable name, for example RELAY_API_KEY." };
|
|
21470
|
+
}
|
|
21471
|
+
return { ok: true, value: name };
|
|
20755
21472
|
}
|
|
20756
21473
|
function isLoopbackHostname(hostname) {
|
|
20757
21474
|
const normalized = hostname.replace(/^\[|\]$/gu, "").toLocaleLowerCase();
|
|
20758
21475
|
return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "::1" || /^127(?:\.\d{1,3}){3}$/u.test(normalized);
|
|
20759
21476
|
}
|
|
20760
21477
|
function OnboardingApp({ initialConfig, saveConfig, onFinish }) {
|
|
20761
|
-
const colorEnabled = initialConfig.ui.color &&
|
|
21478
|
+
const colorEnabled = initialConfig.ui.color && resolveTerminalAccessibility().color;
|
|
20762
21479
|
const theme = useMemo2(() => resolveThemeWithColor(initialConfig.ui.theme, colorEnabled), [colorEnabled, initialConfig.ui.theme]);
|
|
20763
21480
|
return /* @__PURE__ */ jsx5(ThemeProvider, { theme, children: /* @__PURE__ */ jsx5(OnboardingFlow, { initialConfig, saveConfig, onFinish }) });
|
|
20764
21481
|
}
|
|
@@ -20842,9 +21559,8 @@ function OnboardingScreen({ state, dispatch, width, compact: compact3 = false })
|
|
|
20842
21559
|
!compact3 ? /* @__PURE__ */ jsx5(Text6, { color: theme.muted, wrap: "wrap", children: descriptionForStep(state) }) : null,
|
|
20843
21560
|
summary ? /* @__PURE__ */ jsx5(Text6, { color: theme.dim, children: truncateDisplay(summary, headerWidth) }) : null,
|
|
20844
21561
|
!compact3 ? /* @__PURE__ */ jsx5(Box4, { height: 1 }) : null,
|
|
20845
|
-
state.step === "
|
|
20846
|
-
state.step === "
|
|
20847
|
-
state.step === "relay-protocol" ? /* @__PURE__ */ jsx5(OptionList, { options: relayProtocols, selected: state.selected, marker, width: headerWidth, compact: compact3 }) : null,
|
|
21562
|
+
state.step === "relay-protocol" ? /* @__PURE__ */ jsx5(OptionList, { options: relayProtocols2, selected: state.selected, marker, width: headerWidth, compact: compact3 }) : null,
|
|
21563
|
+
state.step === "auth" ? /* @__PURE__ */ jsx5(OptionList, { options: authMethods, selected: state.selected, marker, width: headerWidth, compact: compact3 }) : null,
|
|
20848
21564
|
inputField ? /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", children: [
|
|
20849
21565
|
/* @__PURE__ */ jsxs5(Box4, { children: [
|
|
20850
21566
|
/* @__PURE__ */ jsx5(Text6, { color: theme.textStrong, bold: true, children: inputField.label }),
|
|
@@ -20861,8 +21577,7 @@ function OnboardingScreen({ state, dispatch, width, compact: compact3 = false })
|
|
|
20861
21577
|
value: state.draft[inputField.field],
|
|
20862
21578
|
onChange: (value) => dispatch({ type: "INPUT", field: inputField.field, value }),
|
|
20863
21579
|
onSubmit: (value) => dispatch({ type: "SUBMIT_INPUT", field: inputField.field, value }),
|
|
20864
|
-
placeholder: inputField.placeholder
|
|
20865
|
-
...inputField.field === "apiKey" ? { mask: ascii ? "*" : "\u2022" } : {}
|
|
21580
|
+
placeholder: inputField.placeholder
|
|
20866
21581
|
}
|
|
20867
21582
|
) })
|
|
20868
21583
|
] })
|
|
@@ -20899,13 +21614,13 @@ function OptionList({ options, selected, marker, width, compact: compact3 }) {
|
|
|
20899
21614
|
}
|
|
20900
21615
|
function Confirmation({ state, width }) {
|
|
20901
21616
|
const theme = useTheme();
|
|
20902
|
-
const relay = state.draft.method === "relay";
|
|
20903
21617
|
const values = [
|
|
20904
|
-
["
|
|
20905
|
-
["Protocol",
|
|
20906
|
-
|
|
21618
|
+
["Connection", DEFAULT_CONNECTION_NAME],
|
|
21619
|
+
["Protocol", relayLabel(state.draft.relayProtocol)],
|
|
21620
|
+
["Inference", redactEndpoint(state.draft.baseUrl)],
|
|
21621
|
+
["Models", state.draft.modelsBaseUrl ? redactEndpoint(state.draft.modelsBaseUrl) : "same base as inference"],
|
|
20907
21622
|
["Model", state.draft.model],
|
|
20908
|
-
["Credential", state.draft.
|
|
21623
|
+
["Credential", state.draft.auth === "env" ? `env:${state.draft.apiKeyEnv} \xB7 configured` : "none"]
|
|
20909
21624
|
];
|
|
20910
21625
|
const tabular = width >= 36;
|
|
20911
21626
|
return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", children: [
|
|
@@ -20917,53 +21632,61 @@ function Confirmation({ state, width }) {
|
|
|
20917
21632
|
] });
|
|
20918
21633
|
}
|
|
20919
21634
|
function menuCount(step2) {
|
|
20920
|
-
if (step2 === "
|
|
20921
|
-
if (step2 === "
|
|
20922
|
-
if (step2 === "relay-protocol") return relayProtocols.length;
|
|
21635
|
+
if (step2 === "relay-protocol") return relayProtocols2.length;
|
|
21636
|
+
if (step2 === "auth") return authMethods.length;
|
|
20923
21637
|
return 0;
|
|
20924
21638
|
}
|
|
20925
21639
|
function inputFieldForStep(state) {
|
|
20926
|
-
if (state.step === "endpoint") return { field: "baseUrl", label: "
|
|
21640
|
+
if (state.step === "endpoint") return { field: "baseUrl", label: "Inference base URL", placeholder: "https://relay.example/v1", required: true };
|
|
21641
|
+
if (state.step === "models-endpoint") return {
|
|
21642
|
+
field: "modelsBaseUrl",
|
|
21643
|
+
label: "Models base URL",
|
|
21644
|
+
placeholder: state.draft.relayProtocol === "anthropic-messages" ? "https://relay.example/v1" : "blank uses inference base",
|
|
21645
|
+
required: state.draft.relayProtocol === "anthropic-messages"
|
|
21646
|
+
};
|
|
20927
21647
|
if (state.step === "model") return { field: "model", label: "Model identifier", placeholder: "provider-model-id", required: true };
|
|
20928
|
-
if (state.step === "api-key") return {
|
|
21648
|
+
if (state.step === "api-key-env") return {
|
|
21649
|
+
field: "apiKeyEnv",
|
|
21650
|
+
label: "Credential environment variable",
|
|
21651
|
+
placeholder: "RELAY_API_KEY",
|
|
21652
|
+
required: true
|
|
21653
|
+
};
|
|
20929
21654
|
return void 0;
|
|
20930
21655
|
}
|
|
20931
21656
|
function setupStage(state) {
|
|
20932
|
-
const index = state.step === "endpoint" || state.step === "
|
|
20933
|
-
const name = index === 1 ? "
|
|
20934
|
-
return { index, name, progress: `SETUP ${index}/
|
|
21657
|
+
const index = state.step === "endpoint" || state.step === "models-endpoint" ? 2 : state.step === "model" ? 3 : state.step === "auth" || state.step === "api-key-env" ? 4 : state.step === "confirm" || state.step === "saving" ? 5 : 1;
|
|
21658
|
+
const name = index === 1 ? "TRANSPORT" : index === 2 ? "ENDPOINTS" : index === 3 ? "MODEL" : index === 4 ? "AUTH" : "REVIEW";
|
|
21659
|
+
return { index, name, progress: `SETUP ${index}/5` };
|
|
20935
21660
|
}
|
|
20936
21661
|
function stageDivider(ascii, width) {
|
|
20937
21662
|
return (ascii ? "-" : "\u2500").repeat(Math.max(1, width));
|
|
20938
21663
|
}
|
|
20939
21664
|
function connectionSummary(state) {
|
|
20940
|
-
if (!state.draft.
|
|
20941
|
-
const parts = [
|
|
20942
|
-
|
|
20943
|
-
state.draft.method === "relay" ? relayLabel(state.draft.relayProtocol) : providerLabel(state.draft.provider)
|
|
20944
|
-
);
|
|
21665
|
+
if (!state.draft.relayProtocol) return "";
|
|
21666
|
+
const parts = ["Relay"];
|
|
21667
|
+
parts.push(relayLabel(state.draft.relayProtocol));
|
|
20945
21668
|
if (state.draft.baseUrl) parts.push(redactEndpoint(state.draft.baseUrl));
|
|
20946
21669
|
if (state.draft.model) parts.push(state.draft.model);
|
|
20947
21670
|
return parts.join(" / ");
|
|
20948
21671
|
}
|
|
20949
21672
|
function titleForStep(step2) {
|
|
20950
|
-
if (step2 === "
|
|
20951
|
-
if (step2 === "
|
|
20952
|
-
if (step2 === "
|
|
20953
|
-
if (step2 === "endpoint") return "Enter the endpoint base URL";
|
|
21673
|
+
if (step2 === "relay-protocol") return "Choose the relay protocol";
|
|
21674
|
+
if (step2 === "endpoint") return "Enter the inference base URL";
|
|
21675
|
+
if (step2 === "models-endpoint") return "Enter the model catalog base URL";
|
|
20954
21676
|
if (step2 === "model") return "Enter the model identifier";
|
|
20955
|
-
if (step2 === "
|
|
21677
|
+
if (step2 === "auth") return "Choose relay authentication";
|
|
21678
|
+
if (step2 === "api-key-env") return "Reference the credential environment";
|
|
20956
21679
|
if (step2 === "confirm") return "Review and save";
|
|
20957
21680
|
return "Saving configuration";
|
|
20958
21681
|
}
|
|
20959
21682
|
function descriptionForStep(state) {
|
|
20960
|
-
if (state.step === "
|
|
20961
|
-
if (state.step === "official-provider") return "Use the API credential issued by the selected provider.";
|
|
20962
|
-
if (state.step === "relay-protocol") return "The protocol is explicit so requests and credentials are never guessed.";
|
|
21683
|
+
if (state.step === "relay-protocol") return "Skein connects through third-party relays only. Responses is recommended; Chat Completions and Anthropic Messages remain explicit compatibility transports.";
|
|
20963
21684
|
if (state.step === "endpoint") return "Remote endpoints require HTTPS. Loopback development servers may use HTTP.";
|
|
20964
|
-
if (state.step === "
|
|
20965
|
-
if (state.step === "
|
|
20966
|
-
if (state.step === "
|
|
21685
|
+
if (state.step === "models-endpoint") return state.draft.relayProtocol === "anthropic-messages" ? "Anthropic inference bases often differ from the OpenAI-style /models directory, so this value is required." : "Leave blank when GET /models uses the same base as inference.";
|
|
21686
|
+
if (state.step === "model") return "Use the exact model identifier returned or documented by the relay.";
|
|
21687
|
+
if (state.step === "auth") return "Credentials are referenced from the environment and are never written to Skein configuration.";
|
|
21688
|
+
if (state.step === "api-key-env") return "Enter the variable name only. It must already exist in this process environment.";
|
|
21689
|
+
if (state.step === "confirm") return "Only redacted endpoints, model metadata, and the credential variable name are saved.";
|
|
20967
21690
|
return "The configuration is saved only after this step succeeds.";
|
|
20968
21691
|
}
|
|
20969
21692
|
function footerForStep(state, width) {
|
|
@@ -20974,13 +21697,13 @@ function footerForStep(state, width) {
|
|
|
20974
21697
|
return width < 48 ? "Enter next \xB7 Esc back" : "Enter continue \xB7 Esc back \xB7 Ctrl+C cancel";
|
|
20975
21698
|
}
|
|
20976
21699
|
function relayLabel(protocol) {
|
|
20977
|
-
|
|
20978
|
-
|
|
20979
|
-
|
|
20980
|
-
return officialProviders.find((item) => item.provider === provider)?.label ?? provider ?? "not selected";
|
|
21700
|
+
if (protocol === "anthropic-messages") return "Anthropic Messages";
|
|
21701
|
+
if (protocol === "openai-chat") return "OpenAI Chat Completions";
|
|
21702
|
+
return "OpenAI Responses";
|
|
20981
21703
|
}
|
|
20982
21704
|
async function runFirstRunOnboarding(initialConfig, options = {}) {
|
|
20983
21705
|
let result;
|
|
21706
|
+
const terminalAccessibility = resolveTerminalAccessibility();
|
|
20984
21707
|
const instance = render4(
|
|
20985
21708
|
/* @__PURE__ */ jsx5(
|
|
20986
21709
|
OnboardingApp,
|
|
@@ -20998,11 +21721,13 @@ async function runFirstRunOnboarding(initialConfig, options = {}) {
|
|
|
20998
21721
|
...options.stderr ? { stderr: options.stderr } : {},
|
|
20999
21722
|
exitOnCtrlC: false,
|
|
21000
21723
|
patchConsole: false,
|
|
21001
|
-
incrementalRendering:
|
|
21724
|
+
incrementalRendering: terminalAccessibility.incrementalRendering,
|
|
21725
|
+
isScreenReaderEnabled: terminalAccessibility.screenReader,
|
|
21002
21726
|
kittyKeyboard: resolveKittyKeyboardConfig()
|
|
21003
21727
|
}
|
|
21004
21728
|
);
|
|
21005
21729
|
await instance.waitUntilExit();
|
|
21730
|
+
if (terminalAccessibility.screenReader) (options.stdout ?? process.stdout).write("\n");
|
|
21006
21731
|
return result ?? { status: "cancelled" };
|
|
21007
21732
|
}
|
|
21008
21733
|
|
|
@@ -21016,7 +21741,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
|
|
|
21016
21741
|
import stripAnsi6 from "strip-ansi";
|
|
21017
21742
|
|
|
21018
21743
|
// src/mcp/tool.ts
|
|
21019
|
-
import { createHash as
|
|
21744
|
+
import { createHash as createHash19 } from "node:crypto";
|
|
21020
21745
|
import stripAnsi4 from "strip-ansi";
|
|
21021
21746
|
var MAX_ARGUMENT_BYTES = 256e3;
|
|
21022
21747
|
var MAX_DESCRIPTION_LENGTH = 4e3;
|
|
@@ -21252,7 +21977,7 @@ function fitToolName(name, identity2) {
|
|
|
21252
21977
|
return `${name.slice(0, 64 - suffix.length).replace(/_+$/g, "")}${suffix}`;
|
|
21253
21978
|
}
|
|
21254
21979
|
function shortHash(value) {
|
|
21255
|
-
return
|
|
21980
|
+
return createHash19("sha256").update(value).digest("hex").slice(0, 8);
|
|
21256
21981
|
}
|
|
21257
21982
|
function sanitizeOutputText(value) {
|
|
21258
21983
|
return stripAnsi4(value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
|
|
@@ -21418,7 +22143,7 @@ function isLoopbackHost(hostname) {
|
|
|
21418
22143
|
}
|
|
21419
22144
|
|
|
21420
22145
|
// src/mcp/capabilities.ts
|
|
21421
|
-
import { createHash as
|
|
22146
|
+
import { createHash as createHash20 } from "node:crypto";
|
|
21422
22147
|
import { homedir as homedir4 } from "node:os";
|
|
21423
22148
|
import { basename as basename12, isAbsolute as isAbsolute9, relative as relative11, resolve as resolve23 } from "node:path";
|
|
21424
22149
|
import stripAnsi5 from "strip-ansi";
|
|
@@ -21447,7 +22172,7 @@ function capabilityFingerprint(name, config, workspace = process.cwd()) {
|
|
|
21447
22172
|
envNames: Object.keys(config.env ?? {}).sort(),
|
|
21448
22173
|
headerNames: Object.keys(config.headers ?? {}).map((key) => key.toLocaleLowerCase()).sort()
|
|
21449
22174
|
};
|
|
21450
|
-
return
|
|
22175
|
+
return createHash20("sha256").update(stableJson2({ manifest, privateTransport })).digest("hex");
|
|
21451
22176
|
}
|
|
21452
22177
|
function searchMcpCapabilities(config, query) {
|
|
21453
22178
|
const terms = new Set(sanitizeText(query, 500).toLocaleLowerCase().match(/[\p{L}\p{N}_-]{2,}/gu) ?? []);
|
|
@@ -23154,7 +23879,7 @@ process.on("warning", (warning) => {
|
|
|
23154
23879
|
var program = new Command();
|
|
23155
23880
|
program.enablePositionalOptions();
|
|
23156
23881
|
program.name(PRODUCT_COMMAND).description("A context-first, model-agnostic coding agent with an auditable workspace.").version(package_default.version).showSuggestionAfterError();
|
|
23157
|
-
program.argument("[prompt...]", "instruction for the agent").option("-p, --print", "run once and print the result").option("-a, --ask", "retrieval and inspection mode; mutation tools are denied").option("--plan", "read-only planning mode; propose changes without mutating the workspace").option("-q, --quiet", "print only the final response in text mode").addOption(new Option("--output-format <format>", "text, json, or stream-json").choices(["text", "json", "stream-json"]).default("text")).option("--compact", "reduce progress output in print mode").option("--yes", "approve all non-denied tool requests for this run").option("--auto-edit", "approve read/write requests and ask before shell/Git/network").option("--trust-project-config", "allow executable and security-sensitive settings from project config").option("--queue <prompt>", "run an additional prompt after the first one", collect, []).option("-w, --workspace <path>", "primary workspace root", process.cwd()).option("--add-workspace <path>", "additional workspace root", collect, []).option("--config <path>", "explicit config file").option("--provider <provider>", "model provider").option("--model <model>", "model identifier").option("--base-url <url>", "OpenAI-compatible or provider base URL").option("--max-turns <n>", "maximum agent turns").option("--epoch-token-budget <n>", "maximum tokens before an internal context handoff").option("--token-budget <n>", "maximum lifetime tokens across the resumed session").option("--resume [session]", "resume a session by id or prefix").option("-c, --continue", "resume the latest session").option("--no-color", "disable color output").option("--no-checkpoint", "disable pre-mutation checkpoints for this run").action(async (prompts, options) => {
|
|
23882
|
+
program.argument("[prompt...]", "instruction for the agent").option("-p, --print", "run once and print the result").option("-a, --ask", "retrieval and inspection mode; mutation tools are denied").option("--plan", "read-only planning mode; propose changes without mutating the workspace").option("-q, --quiet", "print only the final response in text mode").addOption(new Option("--output-format <format>", "text, json, or stream-json").choices(["text", "json", "stream-json"]).default("text")).option("--compact", "reduce progress output in print mode").option("--yes", "approve all non-denied tool requests for this run").option("--auto-edit", "approve read/write requests and ask before shell/Git/network").option("--trust-project-config", "allow executable and security-sensitive settings from project config").option("--queue <prompt>", "run an additional prompt after the first one", collect, []).option("-w, --workspace <path>", "primary workspace root", process.cwd()).option("--add-workspace <path>", "additional workspace root", collect, []).option("--config <path>", "explicit config file").option("--connection <name>", "named model connection").option("--provider <provider>", "model provider").option("--model <model>", "model identifier").option("--base-url <url>", "OpenAI-compatible or provider base URL").option("--max-turns <n>", "maximum agent turns").option("--epoch-token-budget <n>", "maximum tokens before an internal context handoff").option("--token-budget <n>", "maximum lifetime tokens across the resumed session").option("--resume [session]", "resume a session by id or prefix").option("-c, --continue", "resume the latest session").option("--no-color", "disable color output").option("--no-checkpoint", "disable pre-mutation checkpoints for this run").action(async (prompts, options) => {
|
|
23158
23883
|
await runChat(prompts, options);
|
|
23159
23884
|
});
|
|
23160
23885
|
program.command("init").description("Create a project-local config (preserving an existing .mosaic namespace)").option("-w, --workspace <path>", "workspace root").option("--provider <provider>", "openai, anthropic, gemini, or compatible", "openai").option("--model <model>", "model identifier").option("--base-url <url>", "provider base URL").option("--api-key <key>", "store a provider key in project config (prefer env vars)").option("--index", "build the index after writing config").option("--yes", "use defaults without prompting").action(async (options) => {
|
|
@@ -23486,31 +24211,36 @@ agentsCommand.command("list").description("List built-in and discovered expert p
|
|
|
23486
24211
|
`);
|
|
23487
24212
|
}
|
|
23488
24213
|
});
|
|
23489
|
-
agentsCommand.command("setup").description("Configure one shared model connection and team defaults").option("-w, --workspace <path>", "workspace used to resolve current defaults").option("--name <name>", "connection name").option("--provider <provider>", "
|
|
24214
|
+
agentsCommand.command("setup").description("Configure one shared model connection and team defaults").option("-w, --workspace <path>", "workspace used to resolve current defaults").option("--name <name>", "connection name").option("--provider <provider>", "relay provider; only compatible is supported").option("--protocol <protocol>", "openai-responses, openai-chat, or anthropic-messages").option("--base-url <url>", "relay inference base URL").option("--models-base-url <url>", "separate OpenAI-compatible base URL for GET /models").option("--auth <type>", "connection authentication: env or none").option("--api-key-env <name>", "environment variable containing the credential").option("--model <model>", "default model identifier").option("--yes", "use supplied or existing defaults without prompting").option("--json", "print JSON").action(async (options) => {
|
|
23490
24215
|
await runAgentSetup(options);
|
|
23491
24216
|
});
|
|
23492
24217
|
agentsCommand.command("connections").description("List named model endpoints and credential references").option("-w, --workspace <path>", "workspace root").option("--config <path>", "explicit config file").option("--json", "print JSON").action(async (options) => {
|
|
23493
24218
|
const workspace = workspaceOption(options.workspace);
|
|
23494
24219
|
const config = await runtimeConfig(workspace, runtimeOptions(options));
|
|
23495
|
-
const
|
|
23496
|
-
|
|
24220
|
+
const catalog = discoverConnectionCatalog(config);
|
|
24221
|
+
const connections = catalog.profiles.map((connection) => ({
|
|
24222
|
+
name: connection.id,
|
|
23497
24223
|
provider: connection.provider,
|
|
24224
|
+
protocol: connection.protocol,
|
|
24225
|
+
source: connection.source,
|
|
23498
24226
|
endpoint: redactEndpoint(connection.baseUrl),
|
|
23499
|
-
|
|
23500
|
-
|
|
23501
|
-
|
|
24227
|
+
modelsEndpoint: redactEndpoint(connection.modelsBaseUrl ?? connection.baseUrl),
|
|
24228
|
+
credentials: connectionCredentialReference(connection),
|
|
24229
|
+
routes: Object.values(config.agents?.routes ?? {}).filter((route) => route.connection === connection.id).length,
|
|
24230
|
+
default: catalog.defaultConnection === connection.id,
|
|
24231
|
+
complete: connectionRuntimeCatalog({ profiles: [connection] }).profiles[0]?.complete ?? false
|
|
23502
24232
|
}));
|
|
23503
24233
|
if (options.json) printObject(connections, true);
|
|
23504
24234
|
else if (!connections.length) process.stdout.write("No named model connections configured.\n");
|
|
23505
24235
|
else for (const connection of connections) {
|
|
23506
|
-
process.stdout.write(`${connection.name.padEnd(16)} ${connection.
|
|
24236
|
+
process.stdout.write(`${connection.name.padEnd(16)} ${connection.protocol.padEnd(20)} ${connection.credentials.padEnd(28)} ${connection.source.padEnd(11)} ${connection.complete ? "ready" : "incomplete"}${connection.default ? " + default" : ""} inference=${connection.endpoint} models=${connection.modelsEndpoint}
|
|
23507
24237
|
`);
|
|
23508
24238
|
}
|
|
23509
24239
|
});
|
|
23510
24240
|
agentsCommand.command("models <connection>").description("List model IDs exposed by a named compatible connection").option("-w, --workspace <path>", "workspace root").option("--config <path>", "explicit config file").option("--json", "print JSON").action(async (connectionName, options) => {
|
|
23511
24241
|
const workspace = workspaceOption(options.workspace);
|
|
23512
24242
|
const config = await runtimeConfig(workspace, runtimeOptions(options));
|
|
23513
|
-
const connection = config.
|
|
24243
|
+
const connection = discoverConnectionCatalog(config).profiles.find(({ id }) => id === connectionName);
|
|
23514
24244
|
if (!connection) throw new Error(`Unknown model connection: ${connectionName}`);
|
|
23515
24245
|
const models = await listConnectionModels(connection);
|
|
23516
24246
|
if (options.json) printObject(models, true);
|
|
@@ -23869,14 +24599,15 @@ async function runChat(prompts, options) {
|
|
|
23869
24599
|
const firstPrompt = [...prompts, stdinPrompt].filter(Boolean).join("\n\n").trim();
|
|
23870
24600
|
if (shouldPrint && !firstPrompt) throw new Error("Provide a prompt argument or pipe input on stdin.");
|
|
23871
24601
|
const workspace = resolve27(options.workspace);
|
|
23872
|
-
|
|
24602
|
+
const connectionSelection = shouldPrint ? "required" : "interactive";
|
|
24603
|
+
let config = await runtimeConfig(workspace, { ...options, connectionSelection });
|
|
23873
24604
|
let completedOnboarding = false;
|
|
23874
24605
|
if (!shouldPrint && needsFirstRunOnboarding(config)) {
|
|
23875
24606
|
if (!options.config) {
|
|
23876
24607
|
const onboarding = await runFirstRunOnboarding(config);
|
|
23877
24608
|
if (onboarding.status === "cancelled") return;
|
|
23878
24609
|
completedOnboarding = true;
|
|
23879
|
-
config = await runtimeConfig(workspace, options);
|
|
24610
|
+
config = await runtimeConfig(workspace, { ...options, connectionSelection });
|
|
23880
24611
|
}
|
|
23881
24612
|
}
|
|
23882
24613
|
validateModelSetup(config);
|
|
@@ -23925,7 +24656,8 @@ async function runChat(prompts, options) {
|
|
|
23925
24656
|
format: options.outputFormat,
|
|
23926
24657
|
quiet: options.quiet ?? false,
|
|
23927
24658
|
compact: options.compact ?? false,
|
|
23928
|
-
color: (options.color ?? config.ui.color) && !process.env.NO_COLOR
|
|
24659
|
+
color: (options.color ?? config.ui.color) && !process.env.NO_COLOR,
|
|
24660
|
+
...config.activeConnection ? { connection: config.activeConnection } : {}
|
|
23929
24661
|
});
|
|
23930
24662
|
const colorOutput = (options.color ?? config.ui.color) && !process.env.NO_COLOR;
|
|
23931
24663
|
const requestPermission = options.yes ? async () => true : options.autoEdit ? async (_call, category, reason) => category === "read" || category === "write" ? true : askConsolePermission(_call, category, colorOutput, reason) : async (call, category, reason) => askConsolePermission(call, category, colorOutput, reason);
|
|
@@ -24045,20 +24777,23 @@ async function runAgentSetup(options) {
|
|
|
24045
24777
|
const currentConnection = current.agents?.connections?.[currentName];
|
|
24046
24778
|
let name = options.name ?? currentName;
|
|
24047
24779
|
let provider = validateProvider(options.provider ?? currentConnection?.provider ?? "compatible");
|
|
24780
|
+
let protocol = validateConnectionProtocol(options.protocol ?? currentConnection?.protocol ?? "openai-responses");
|
|
24048
24781
|
let baseUrl = options.baseUrl ?? currentConnection?.baseUrl ?? "";
|
|
24049
|
-
let
|
|
24782
|
+
let modelsBaseUrl = options.modelsBaseUrl ?? currentConnection?.modelsBaseUrl ?? "";
|
|
24783
|
+
let auth = validateConnectionAuth(options.auth ?? currentConnection?.auth?.type ?? "env");
|
|
24784
|
+
const currentApiKeyEnv = currentConnection?.apiKeyEnv ?? (currentConnection?.auth?.type === "env" ? currentConnection.auth.name : void 0);
|
|
24785
|
+
let apiKeyEnv = options.apiKeyEnv ?? (auth === "env" ? currentApiKeyEnv ?? providerEnvironment(provider) : "");
|
|
24050
24786
|
let model = options.model ?? current.agents?.defaultModel ?? current.model.model;
|
|
24051
24787
|
if (!options.yes && process.stdin.isTTY && process.stdout.isTTY) {
|
|
24052
24788
|
const readline = createInterface2({ input, output });
|
|
24053
24789
|
try {
|
|
24054
24790
|
name = await question(readline, "Connection name", name);
|
|
24055
|
-
|
|
24056
|
-
|
|
24057
|
-
|
|
24058
|
-
|
|
24059
|
-
|
|
24060
|
-
|
|
24061
|
-
apiKeyEnv = await question(readline, "Credential environment variable", apiKeyEnv || providerEnvironment(provider));
|
|
24791
|
+
provider = validateProvider(await question(readline, "Relay provider", provider));
|
|
24792
|
+
protocol = validateConnectionProtocol(await question(readline, "Inference protocol", protocol));
|
|
24793
|
+
baseUrl = await question(readline, "Inference base URL", baseUrl);
|
|
24794
|
+
modelsBaseUrl = await question(readline, "Models base URL (optional unless Anthropic)", modelsBaseUrl);
|
|
24795
|
+
auth = validateConnectionAuth(await question(readline, "Authentication (env or none)", auth));
|
|
24796
|
+
apiKeyEnv = auth === "env" ? await question(readline, "Credential environment variable", apiKeyEnv || providerEnvironment(provider)) : "";
|
|
24062
24797
|
model = await question(readline, "Default model", model);
|
|
24063
24798
|
} finally {
|
|
24064
24799
|
readline.close();
|
|
@@ -24067,7 +24802,10 @@ async function runAgentSetup(options) {
|
|
|
24067
24802
|
const setup = createAgentConnectionSetup({
|
|
24068
24803
|
name,
|
|
24069
24804
|
provider,
|
|
24805
|
+
protocol,
|
|
24070
24806
|
...baseUrl ? { baseUrl } : {},
|
|
24807
|
+
...modelsBaseUrl ? { modelsBaseUrl } : {},
|
|
24808
|
+
auth,
|
|
24071
24809
|
...apiKeyEnv ? { apiKeyEnv } : {},
|
|
24072
24810
|
defaultModel: model
|
|
24073
24811
|
});
|
|
@@ -24077,7 +24815,10 @@ async function runAgentSetup(options) {
|
|
|
24077
24815
|
path,
|
|
24078
24816
|
connection: setup.defaultConnection,
|
|
24079
24817
|
provider,
|
|
24818
|
+
protocol,
|
|
24080
24819
|
endpoint: redactEndpoint(baseUrl),
|
|
24820
|
+
modelsEndpoint: redactEndpoint(modelsBaseUrl || baseUrl),
|
|
24821
|
+
auth,
|
|
24081
24822
|
apiKeyEnv: apiKeyEnv || null,
|
|
24082
24823
|
credentialConfigured,
|
|
24083
24824
|
defaultModel: setup.defaultModel
|
|
@@ -24088,11 +24829,11 @@ async function runAgentSetup(options) {
|
|
|
24088
24829
|
}
|
|
24089
24830
|
process.stdout.write(`${chalk4.green(cliGlyphs.success)} Saved shared connection ${setup.defaultConnection} to ${path}
|
|
24090
24831
|
`);
|
|
24091
|
-
process.stdout.write(` Default: ${
|
|
24832
|
+
process.stdout.write(` Default: ${protocol}/${setup.defaultModel} via ${redactEndpoint(baseUrl)}
|
|
24092
24833
|
`);
|
|
24093
|
-
process.stdout.write(`
|
|
24834
|
+
process.stdout.write(` Models: ${PRODUCT_COMMAND} agents models ${setup.defaultConnection} via ${redactEndpoint(modelsBaseUrl || baseUrl)}
|
|
24094
24835
|
`);
|
|
24095
|
-
process.stdout.write(`
|
|
24836
|
+
process.stdout.write(` Credential: ${auth === "none" ? "none" : `env:${apiKeyEnv} (${credentialConfigured ? "configured" : "not set"})`}
|
|
24096
24837
|
`);
|
|
24097
24838
|
process.stdout.write(` Routes: ${PRODUCT_COMMAND} agents list
|
|
24098
24839
|
`);
|
|
@@ -24108,14 +24849,40 @@ async function runtimeConfig(workspaceInput, options) {
|
|
|
24108
24849
|
...(options.addWorkspace ?? []).map((root) => resolve27(workspace, root))
|
|
24109
24850
|
];
|
|
24110
24851
|
const provider = options.provider ? validateProvider(options.provider) : loaded.model.provider;
|
|
24852
|
+
const legacyModel = resolveRuntimeModel(loaded.model, {
|
|
24853
|
+
provider,
|
|
24854
|
+
...options.model ? { model: options.model } : {},
|
|
24855
|
+
...options.baseUrl ? { baseUrl: options.baseUrl } : {}
|
|
24856
|
+
});
|
|
24857
|
+
const catalog = discoverConnectionCatalog(loaded);
|
|
24858
|
+
let model = legacyModel;
|
|
24859
|
+
let activeConnection = legacyConnectionRuntimeInfo(legacyModel);
|
|
24860
|
+
if (options.provider || options.baseUrl) {
|
|
24861
|
+
if (options.connection) throw new Error("--connection cannot be combined with --provider or --base-url.");
|
|
24862
|
+
activeConnection = { ...activeConnection, id: "cli", source: "cli" };
|
|
24863
|
+
} else if (options.connectionSelection !== "inspect" || options.connection) {
|
|
24864
|
+
let selection = planConnectionSelection(catalog, process.env, options.connection);
|
|
24865
|
+
if (selection.kind === "ambiguous") {
|
|
24866
|
+
if (options.connectionSelection === "interactive") {
|
|
24867
|
+
selection = { kind: "selected", profile: await promptConnectionSelection(selection.profiles) };
|
|
24868
|
+
} else if (options.connectionSelection === "required") {
|
|
24869
|
+
throw new Error(`Multiple complete model connections found: ${selection.profiles.map(({ id }) => id).join(", ")}. Pass --connection <name>.`);
|
|
24870
|
+
}
|
|
24871
|
+
}
|
|
24872
|
+
if (selection.kind === "selected") {
|
|
24873
|
+
const resolved = resolveConnectionModel(legacyModel, selection.profile, {
|
|
24874
|
+
...options.model ? { model: options.model } : {}
|
|
24875
|
+
});
|
|
24876
|
+
model = resolved.model;
|
|
24877
|
+
activeConnection = resolved.activeConnection;
|
|
24878
|
+
}
|
|
24879
|
+
}
|
|
24111
24880
|
return {
|
|
24112
24881
|
...loaded,
|
|
24113
24882
|
workspaceRoots: [...new Set(roots)],
|
|
24114
|
-
model
|
|
24115
|
-
|
|
24116
|
-
|
|
24117
|
-
...options.baseUrl ? { baseUrl: options.baseUrl } : {}
|
|
24118
|
-
}),
|
|
24883
|
+
model,
|
|
24884
|
+
connectionCatalog: connectionRuntimeCatalog(catalog),
|
|
24885
|
+
activeConnection,
|
|
24119
24886
|
context: loaded.context,
|
|
24120
24887
|
agent: {
|
|
24121
24888
|
...loaded.agent,
|
|
@@ -24126,6 +24893,23 @@ async function runtimeConfig(workspaceInput, options) {
|
|
|
24126
24893
|
ui: { ...loaded.ui, ...options.color === false ? { color: false } : {} }
|
|
24127
24894
|
};
|
|
24128
24895
|
}
|
|
24896
|
+
async function promptConnectionSelection(profiles) {
|
|
24897
|
+
process.stdout.write("Multiple model connections are ready:\n");
|
|
24898
|
+
profiles.forEach((profile, index) => {
|
|
24899
|
+
process.stdout.write(` ${index + 1}. ${profile.id} ${profile.provider}/${profile.defaultModel ?? defaultModelForProvider(profile.provider)} ${redactEndpoint(profile.baseUrl)}
|
|
24900
|
+
`);
|
|
24901
|
+
});
|
|
24902
|
+
const readline = createInterface2({ input, output });
|
|
24903
|
+
try {
|
|
24904
|
+
const answer = (await question(readline, "Select connection by number or name", "")).trim();
|
|
24905
|
+
const numeric2 = Number(answer);
|
|
24906
|
+
const selected = Number.isInteger(numeric2) && numeric2 >= 1 ? profiles[numeric2 - 1] : profiles.find(({ id }) => id === answer);
|
|
24907
|
+
if (!selected) throw new Error(`Unknown connection selection ${answer || "<empty>"}. Pass --connection <name> to choose explicitly.`);
|
|
24908
|
+
return selected;
|
|
24909
|
+
} finally {
|
|
24910
|
+
readline.close();
|
|
24911
|
+
}
|
|
24912
|
+
}
|
|
24129
24913
|
async function loadSessionSelector(store, selector) {
|
|
24130
24914
|
const summaries = await store.list();
|
|
24131
24915
|
if (!summaries.length) {
|
|
@@ -24297,6 +25081,7 @@ function workspaceOption(value) {
|
|
|
24297
25081
|
function runtimeOptions(options) {
|
|
24298
25082
|
const root = program.opts();
|
|
24299
25083
|
const config = options.config ?? root.config;
|
|
25084
|
+
const connection = options.connection ?? root.connection;
|
|
24300
25085
|
const provider = options.provider ?? root.provider;
|
|
24301
25086
|
const model = options.model ?? root.model;
|
|
24302
25087
|
const baseUrl = options.baseUrl ?? root.baseUrl;
|
|
@@ -24305,6 +25090,7 @@ function runtimeOptions(options) {
|
|
|
24305
25090
|
return {
|
|
24306
25091
|
addWorkspace: [...root.addWorkspace ?? [], ...options.addWorkspace ?? []],
|
|
24307
25092
|
...config ? { config } : {},
|
|
25093
|
+
...connection ? { connection } : {},
|
|
24308
25094
|
...provider ? { provider } : {},
|
|
24309
25095
|
...model ? { model } : {},
|
|
24310
25096
|
...baseUrl ? { baseUrl } : {},
|
|
@@ -24324,6 +25110,14 @@ function validateProvider(value) {
|
|
|
24324
25110
|
if (value === "openai" || value === "anthropic" || value === "gemini" || value === "compatible") return value;
|
|
24325
25111
|
throw new Error(`Unknown provider ${value}; use openai, anthropic, gemini, or compatible.`);
|
|
24326
25112
|
}
|
|
25113
|
+
function validateConnectionProtocol(value) {
|
|
25114
|
+
if (value === "openai-responses" || value === "openai-chat" || value === "anthropic-messages") return value;
|
|
25115
|
+
throw new Error(`Unknown relay protocol ${value}; use openai-responses, openai-chat, or anthropic-messages.`);
|
|
25116
|
+
}
|
|
25117
|
+
function validateConnectionAuth(value) {
|
|
25118
|
+
if (value === "env" || value === "none") return value;
|
|
25119
|
+
throw new Error(`Unknown connection authentication ${value}; use env or none.`);
|
|
25120
|
+
}
|
|
24327
25121
|
function environmentName(provider) {
|
|
24328
25122
|
return providerEnvironment(provider);
|
|
24329
25123
|
}
|