@desplega.ai/agent-swarm 1.79.4 → 1.80.1
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/openapi.json +496 -32
- package/package.json +14 -6
- package/src/artifact-sdk/server.ts +2 -1
- package/src/be/db.ts +102 -31
- package/src/be/migrations/063_cost_context_schema_relax.sql +133 -0
- package/src/be/migrations/064_scripts.sql +39 -0
- package/src/be/migrations/065_script_embeddings.sql +7 -0
- package/src/be/pricing-normalize.ts +81 -0
- package/src/be/scripts/db.ts +391 -0
- package/src/be/scripts/embeddings.ts +231 -0
- package/src/be/scripts/maintenance.ts +9 -0
- package/src/be/scripts/typecheck.ts +193 -0
- package/src/be/seed-pricing.ts +293 -0
- package/src/cli.tsx +22 -5
- package/src/commands/artifact.ts +3 -2
- package/src/commands/claude-managed-setup.ts +21 -4
- package/src/commands/codex-login.ts +5 -3
- package/src/commands/onboard.tsx +2 -1
- package/src/commands/runner.ts +663 -246
- package/src/commands/setup.tsx +5 -3
- package/src/hooks/hook.ts +4 -3
- package/src/http/context.ts +6 -2
- package/src/http/index.ts +126 -68
- package/src/http/memory.ts +28 -0
- package/src/http/openapi.ts +1 -0
- package/src/http/page-proxy.ts +2 -1
- package/src/http/route-def.ts +1 -0
- package/src/http/schedules.ts +37 -0
- package/src/http/scripts.ts +381 -0
- package/src/http/session-data.ts +74 -23
- package/src/linear/outbound.ts +9 -2
- package/src/otel-impl.ts +200 -0
- package/src/otel.ts +132 -0
- package/src/providers/claude-adapter.ts +52 -6
- package/src/providers/claude-managed-adapter.ts +43 -17
- package/src/providers/claude-managed-pricing.ts +34 -0
- package/src/providers/codex-adapter.ts +38 -27
- package/src/providers/codex-models.ts +22 -3
- package/src/providers/devin-adapter.ts +11 -0
- package/src/providers/opencode-adapter.ts +31 -7
- package/src/providers/pi-mono-adapter.ts +39 -7
- package/src/providers/pricing-sources.md +52 -0
- package/src/providers/swarm-events-shared.ts +8 -4
- package/src/providers/types.ts +33 -10
- package/src/scripts-runtime/ctx.ts +23 -0
- package/src/scripts-runtime/eval-harness.ts +39 -0
- package/src/scripts-runtime/executors/native.ts +229 -0
- package/src/scripts-runtime/executors/registry.ts +16 -0
- package/src/scripts-runtime/executors/types.ts +63 -0
- package/src/scripts-runtime/extract-signature.ts +81 -0
- package/src/scripts-runtime/import-allowlist.ts +109 -0
- package/src/scripts-runtime/loader.ts +96 -0
- package/src/scripts-runtime/redacted.ts +48 -0
- package/src/scripts-runtime/sdk-allowlist.ts +29 -0
- package/src/scripts-runtime/stdlib/fetch.ts +46 -0
- package/src/scripts-runtime/stdlib/glob.ts +8 -0
- package/src/scripts-runtime/stdlib/grep.ts +34 -0
- package/src/scripts-runtime/stdlib/index.ts +16 -0
- package/src/scripts-runtime/stdlib/table.ts +17 -0
- package/src/scripts-runtime/swarm-config.ts +35 -0
- package/src/scripts-runtime/swarm-sdk.ts +197 -0
- package/src/scripts-runtime/types/stdlib.d.ts +104 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +86 -0
- package/src/server.ts +18 -0
- package/src/tests/api-key.test.ts +33 -0
- package/src/tests/claude-managed-adapter.test.ts +17 -3
- package/src/tests/claude-managed-setup.test.ts +10 -1
- package/src/tests/codex-adapter.test.ts +20 -19
- package/src/tests/codex-login.test.ts +1 -1
- package/src/tests/context-snapshot.test.ts +2 -2
- package/src/tests/context-window.test.ts +65 -1
- package/src/tests/devin-adapter.test.ts +2 -0
- package/src/tests/http/context-routes.test.ts +161 -0
- package/src/tests/linear-outbound-sync.test.ts +109 -0
- package/src/tests/mcp-tools.test.ts +69 -0
- package/src/tests/migration-063-schema-relax.test.ts +109 -0
- package/src/tests/opencode-adapter.test.ts +146 -1
- package/src/tests/otel-impl-secret-scrubbing.test.ts +33 -0
- package/src/tests/pages-view-count.test.ts +30 -5
- package/src/tests/providers/codex-cost.test.ts +18 -0
- package/src/tests/providers/opencode-cost.test.ts +74 -0
- package/src/tests/providers/pi-cost.test.ts +128 -0
- package/src/tests/redacted.test.ts +29 -0
- package/src/tests/runner-tool-spans.test.ts +268 -0
- package/src/tests/script-executor-conformance.test.ts +142 -0
- package/src/tests/script-executor-registry.test.ts +17 -0
- package/src/tests/scripts-db.test.ts +329 -0
- package/src/tests/scripts-embeddings.test.ts +291 -0
- package/src/tests/scripts-extract-signature.test.ts +47 -0
- package/src/tests/scripts-http.test.ts +350 -0
- package/src/tests/scripts-import-allowlist.test.ts +55 -0
- package/src/tests/scripts-mcp-e2e.test.ts +269 -0
- package/src/tests/scripts-runtime-secret-egress.test.ts +44 -0
- package/src/tests/scripts-runtime.test.ts +289 -0
- package/src/tests/sdk-allowlist.test.ts +59 -0
- package/src/tests/secret-scrubber.test.ts +54 -1
- package/src/tests/session-costs-codex-recompute.test.ts +35 -22
- package/src/tests/session-costs-model-key-normalize.test.ts +271 -0
- package/src/tests/session-costs-recompute-all-providers.test.ts +170 -0
- package/src/tests/store-progress-cost.test.ts +6 -1
- package/src/tests/swarm-config.test.ts +38 -0
- package/src/tests/tool-annotations.test.ts +2 -2
- package/src/tests/tool-call-progress.test.ts +30 -0
- package/src/tests/workflow-e2e.test.ts +218 -0
- package/src/tests/workflow-executors.test.ts +32 -2
- package/src/tests/workflow-input-redaction.test.ts +232 -0
- package/src/tests/workflow-swarm-script.test.ts +273 -0
- package/src/tools/memory-rate.ts +2 -1
- package/src/tools/script-common.ts +88 -0
- package/src/tools/script-delete.ts +35 -0
- package/src/tools/script-query-types.ts +37 -0
- package/src/tools/script-run.ts +43 -0
- package/src/tools/script-search.ts +32 -0
- package/src/tools/script-upsert.ts +43 -0
- package/src/tools/store-progress.ts +16 -60
- package/src/tools/tool-config.ts +7 -0
- package/src/tools/utils.ts +65 -12
- package/src/types.ts +122 -10
- package/src/utils/api-key.ts +28 -0
- package/src/utils/context-window.ts +104 -4
- package/src/utils/page-session.ts +8 -6
- package/src/utils/secret-scrubber.ts +29 -1
- package/src/workflows/engine.ts +12 -4
- package/src/workflows/executors/index.ts +1 -0
- package/src/workflows/executors/registry.ts +2 -0
- package/src/workflows/executors/script.ts +12 -1
- package/src/workflows/executors/swarm-script.ts +170 -0
- package/src/workflows/input.ts +65 -0
- package/src/workflows/recovery.ts +31 -3
- package/src/workflows/resume.ts +43 -5
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
|
|
3
|
+
export type ScriptTypecheckResult = { ok: true } | { ok: false; diagnostics: string[] };
|
|
4
|
+
|
|
5
|
+
export const SCRIPT_SDK_TYPES = `
|
|
6
|
+
export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };
|
|
7
|
+
export type ScriptScope = "agent" | "global";
|
|
8
|
+
export type ScriptFsMode = "none" | "workspace-rw";
|
|
9
|
+
|
|
10
|
+
export interface Redacted<T> {
|
|
11
|
+
readonly __redactedBrand?: T;
|
|
12
|
+
toString(): "<redacted>";
|
|
13
|
+
toJSON(): "<redacted>";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface RedactedStatic {
|
|
17
|
+
value<T>(self: Redacted<T>): T;
|
|
18
|
+
meta<T>(self: Redacted<T>): { type: "system" | "user"; isSecret: boolean };
|
|
19
|
+
isSecret<T>(self: Redacted<T>): boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SwarmConfig {
|
|
23
|
+
apiKey: Redacted<string>;
|
|
24
|
+
agentId: Redacted<string>;
|
|
25
|
+
mcpBaseUrl: Redacted<string>;
|
|
26
|
+
get<T = string>(key: string): Redacted<T> | undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SwarmSdk {
|
|
30
|
+
memory_search(args: { query: string; scope?: "all" | "agent" | "swarm"; limit?: number; source?: string }): Promise<unknown>;
|
|
31
|
+
memory_get(args: { memoryId: string }): Promise<unknown>;
|
|
32
|
+
memory_rate(args: { id: string; useful: boolean; note?: string }): Promise<unknown>;
|
|
33
|
+
task_list(args?: Record<string, unknown>): Promise<unknown>;
|
|
34
|
+
task_get(args: { taskId: string }): Promise<unknown>;
|
|
35
|
+
task_storeProgress(args: Record<string, unknown>): Promise<unknown>;
|
|
36
|
+
kv_get(args: { key: string; namespace?: string }): Promise<unknown>;
|
|
37
|
+
kv_set(args: { key: string; value: unknown; namespace?: string; ttlSeconds?: number; valueType?: "string" | "json" | "integer" }): Promise<unknown>;
|
|
38
|
+
kv_del(args: { key: string; namespace?: string }): Promise<unknown>;
|
|
39
|
+
kv_incr(args: { key: string; by?: number; namespace?: string }): Promise<unknown>;
|
|
40
|
+
kv_list(args?: { prefix?: string; namespace?: string; limit?: number }): Promise<unknown>;
|
|
41
|
+
repo_list(args?: Record<string, unknown>): Promise<unknown>;
|
|
42
|
+
schedule_list(args?: Record<string, unknown>): Promise<unknown>;
|
|
43
|
+
script_search(args: { query?: string; scope?: ScriptScope; limit?: number }): Promise<unknown>;
|
|
44
|
+
script_run(args: { name?: string; source?: string; args?: unknown; intent?: string; scope?: ScriptScope; fsMode?: ScriptFsMode }): Promise<unknown>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ScriptStdlib {
|
|
48
|
+
fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
|
|
49
|
+
fetchJson(input: string | URL | Request, init?: RequestInit): Promise<unknown>;
|
|
50
|
+
grep(pattern: string, files?: string | string[]): Promise<string>;
|
|
51
|
+
glob(pattern: string): Promise<string[]>;
|
|
52
|
+
table(rows: Array<Record<string, unknown>>): string;
|
|
53
|
+
Redacted: RedactedStatic;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ScriptLogger extends Console {}
|
|
57
|
+
|
|
58
|
+
export interface ScriptContext {
|
|
59
|
+
swarm: SwarmSdk & { config: SwarmConfig };
|
|
60
|
+
stdlib: ScriptStdlib;
|
|
61
|
+
logger: ScriptLogger;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// biome-ignore lint/suspicious/noExplicitAny: scripts may narrow their args type at the entrypoint.
|
|
65
|
+
export type ScriptMain = (args: any, ctx: ScriptContext) => unknown | Promise<unknown>;
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
export const SCRIPT_STDLIB_TYPES = `
|
|
69
|
+
declare module "stdlib" {
|
|
70
|
+
export interface Redacted<T> {
|
|
71
|
+
readonly __redactedBrand?: T;
|
|
72
|
+
toString(): "<redacted>";
|
|
73
|
+
toJSON(): "<redacted>";
|
|
74
|
+
}
|
|
75
|
+
export const Redacted: {
|
|
76
|
+
value<T>(self: Redacted<T>): T;
|
|
77
|
+
meta<T>(self: Redacted<T>): { type: "system" | "user"; isSecret: boolean };
|
|
78
|
+
isSecret<T>(self: Redacted<T>): boolean;
|
|
79
|
+
};
|
|
80
|
+
export function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
|
|
81
|
+
export function fetchJson(input: string | URL | Request, init?: RequestInit): Promise<unknown>;
|
|
82
|
+
export function grep(pattern: string, files?: string | string[]): Promise<string>;
|
|
83
|
+
export function glob(pattern: string): Promise<string[]>;
|
|
84
|
+
export function table(rows: Array<Record<string, unknown>>): string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
declare module "swarm-sdk" {
|
|
88
|
+
${SCRIPT_SDK_TYPES.replace(/^/gm, " ")}
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
91
|
+
|
|
92
|
+
const USER_FILE = "/virtual/user-script.ts";
|
|
93
|
+
const CHECK_FILE = "/virtual/check.ts";
|
|
94
|
+
const SDK_FILE = "/virtual/swarm-sdk.d.ts";
|
|
95
|
+
const STDLIB_FILE = "/virtual/stdlib.d.ts";
|
|
96
|
+
|
|
97
|
+
function createCompilerHost(
|
|
98
|
+
files: Map<string, string>,
|
|
99
|
+
options: ts.CompilerOptions,
|
|
100
|
+
): ts.CompilerHost {
|
|
101
|
+
const host = ts.createCompilerHost(options, true);
|
|
102
|
+
const originalGetSourceFile = host.getSourceFile.bind(host);
|
|
103
|
+
|
|
104
|
+
host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
|
|
105
|
+
const normalized = fileName.replace(/\\/g, "/");
|
|
106
|
+
const source = files.get(normalized);
|
|
107
|
+
if (source !== undefined) {
|
|
108
|
+
return ts.createSourceFile(normalized, source, languageVersion, true, ts.ScriptKind.TS);
|
|
109
|
+
}
|
|
110
|
+
return originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
host.fileExists = (fileName) => {
|
|
114
|
+
const normalized = fileName.replace(/\\/g, "/");
|
|
115
|
+
return files.has(normalized) || ts.sys.fileExists(fileName);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
host.readFile = (fileName) => {
|
|
119
|
+
const normalized = fileName.replace(/\\/g, "/");
|
|
120
|
+
return files.get(normalized) ?? ts.sys.readFile(fileName);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
host.resolveModuleNames = (moduleNames, containingFile) =>
|
|
124
|
+
moduleNames.map((moduleName) => {
|
|
125
|
+
if (moduleName === "./user-script") {
|
|
126
|
+
return { resolvedFileName: USER_FILE, extension: ts.Extension.Ts };
|
|
127
|
+
}
|
|
128
|
+
if (moduleName === "swarm-sdk") {
|
|
129
|
+
return { resolvedFileName: SDK_FILE, extension: ts.Extension.Dts };
|
|
130
|
+
}
|
|
131
|
+
if (moduleName === "stdlib") {
|
|
132
|
+
return { resolvedFileName: STDLIB_FILE, extension: ts.Extension.Dts };
|
|
133
|
+
}
|
|
134
|
+
return ts.resolveModuleName(moduleName, containingFile, options, host).resolvedModule;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// In compiled binary mode, TypeScript's lib .d.ts files live alongside
|
|
138
|
+
// typescript.js in /$bunfs/ — but .d.ts files are not embedded in the binary.
|
|
139
|
+
// Redirect lib lookups to TS_LIB_DIR where the Dockerfile copies real copies.
|
|
140
|
+
const tsLibDir = process.env.TS_LIB_DIR;
|
|
141
|
+
if (tsLibDir) {
|
|
142
|
+
host.getDefaultLibLocation = () => tsLibDir;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return host;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function typecheckScript(source: string): ScriptTypecheckResult {
|
|
149
|
+
const options: ts.CompilerOptions = {
|
|
150
|
+
allowImportingTsExtensions: true,
|
|
151
|
+
module: ts.ModuleKind.ESNext,
|
|
152
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
153
|
+
noEmit: true,
|
|
154
|
+
skipLibCheck: true,
|
|
155
|
+
strict: true,
|
|
156
|
+
target: ts.ScriptTarget.ES2022,
|
|
157
|
+
types: [],
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const files = new Map<string, string>([
|
|
161
|
+
[USER_FILE, source],
|
|
162
|
+
[SDK_FILE, SCRIPT_SDK_TYPES],
|
|
163
|
+
[STDLIB_FILE, SCRIPT_STDLIB_TYPES],
|
|
164
|
+
[
|
|
165
|
+
CHECK_FILE,
|
|
166
|
+
`import run from "./user-script";
|
|
167
|
+
import type { ScriptMain } from "swarm-sdk";
|
|
168
|
+
const _scriptMain: ScriptMain = run;
|
|
169
|
+
void _scriptMain;
|
|
170
|
+
`,
|
|
171
|
+
],
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
const host = createCompilerHost(files, options);
|
|
175
|
+
const program = ts.createProgram([USER_FILE, CHECK_FILE, SDK_FILE, STDLIB_FILE], options, host);
|
|
176
|
+
const diagnostics = [
|
|
177
|
+
...program.getSyntacticDiagnostics(),
|
|
178
|
+
...program.getSemanticDiagnostics(),
|
|
179
|
+
].filter((diagnostic) => {
|
|
180
|
+
const fileName = diagnostic.file?.fileName.replace(/\\/g, "/");
|
|
181
|
+
return fileName === USER_FILE || fileName === CHECK_FILE;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
if (diagnostics.length === 0) return { ok: true };
|
|
185
|
+
|
|
186
|
+
const formatted = ts.formatDiagnosticsWithColorAndContext(diagnostics, {
|
|
187
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
188
|
+
getCurrentDirectory: () => "/virtual",
|
|
189
|
+
getNewLine: () => "\n",
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
return { ok: false, diagnostics: formatted.split("\n\n").filter(Boolean) };
|
|
193
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 2 of the cost-tracking plan — seed the `pricing` table at server boot.
|
|
3
|
+
*
|
|
4
|
+
* The vendored models.dev snapshot at `ui/src/lib/modelsdev-cache.json` is the
|
|
5
|
+
* single source of truth for per-token rates. We project it into rows keyed by
|
|
6
|
+
* `(provider, model, token_class)` so the recompute path in
|
|
7
|
+
* `src/http/session-data.ts` can rebuild USD from tokens regardless of which
|
|
8
|
+
* adapter wrote the row.
|
|
9
|
+
*
|
|
10
|
+
* Manual overrides (Anthropic runtime fee, Cognition ACU) live in
|
|
11
|
+
* {@link MANUAL_PRICING_OVERRIDES} — models.dev doesn't surface those.
|
|
12
|
+
*
|
|
13
|
+
* The seeder uses `INSERT OR IGNORE` keyed on the pricing PK
|
|
14
|
+
* `(provider, model, token_class, effective_from)` with `effective_from = 0`,
|
|
15
|
+
* so re-runs on every boot are no-ops once seeded. Operators who need to bump
|
|
16
|
+
* a rate insert a new row with a later `effective_from` via the existing
|
|
17
|
+
* admin route (`POST /api/pricing`) — we don't overwrite seed rows.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { readFileSync } from "node:fs";
|
|
21
|
+
import path from "node:path";
|
|
22
|
+
import type { PricingProvider, PricingTokenClass } from "../types";
|
|
23
|
+
import { getDb } from "./db";
|
|
24
|
+
import { normalizeModelKey } from "./pricing-normalize";
|
|
25
|
+
|
|
26
|
+
interface ModelsDevCostBlock {
|
|
27
|
+
input?: number;
|
|
28
|
+
output?: number;
|
|
29
|
+
cache_read?: number;
|
|
30
|
+
cache_write?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ModelsDevModel {
|
|
34
|
+
id?: string;
|
|
35
|
+
cost?: ModelsDevCostBlock;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface ModelsDevProvider {
|
|
39
|
+
models?: Record<string, ModelsDevModel>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type ModelsDevCache = Record<string, ModelsDevProvider>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Per-harness manual rates that models.dev doesn't carry. Keep the source URL
|
|
46
|
+
* and a verification date next to each entry so {@link MANUAL_PRICING_OVERRIDES}
|
|
47
|
+
* doubles as living documentation.
|
|
48
|
+
*/
|
|
49
|
+
const MANUAL_PRICING_OVERRIDES: Array<{
|
|
50
|
+
provider: PricingProvider;
|
|
51
|
+
model: string;
|
|
52
|
+
tokenClass: PricingTokenClass;
|
|
53
|
+
pricePerMillionUsd: number;
|
|
54
|
+
source: string;
|
|
55
|
+
verified: string; // YYYY-MM-DD
|
|
56
|
+
}> = [
|
|
57
|
+
{
|
|
58
|
+
provider: "claude-managed",
|
|
59
|
+
// '*' = applies regardless of which Claude model the managed run picks.
|
|
60
|
+
// The runtime fee is per session-hour, not per model.
|
|
61
|
+
model: "*",
|
|
62
|
+
tokenClass: "runtime_hour",
|
|
63
|
+
// $0.08 / hour expressed as USD per "million units" so it fits the same
|
|
64
|
+
// rate table. The adapter will multiply by hours, not by tokens — the
|
|
65
|
+
// unit is a convention specific to `runtime_hour`.
|
|
66
|
+
pricePerMillionUsd: 0.08 * 1_000_000,
|
|
67
|
+
source: "https://docs.claude.com/en/api/agent-sdk/managed-runtime#pricing",
|
|
68
|
+
verified: "2026-04-28",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
provider: "devin",
|
|
72
|
+
model: "*",
|
|
73
|
+
tokenClass: "acu",
|
|
74
|
+
pricePerMillionUsd: 2.25 * 1_000_000,
|
|
75
|
+
source: "https://devin.ai/pricing",
|
|
76
|
+
verified: "2026-04-28",
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Adapter-specific shortname → models.dev key. Some adapters report `model`
|
|
82
|
+
* fields the models.dev snapshot doesn't index directly; we map them here.
|
|
83
|
+
*/
|
|
84
|
+
const ANTHROPIC_SHORTNAME_TO_MODELSDEV: Record<string, string> = {
|
|
85
|
+
opus: "claude-opus-4-7",
|
|
86
|
+
sonnet: "claude-sonnet-4-6",
|
|
87
|
+
haiku: "claude-haiku-4-5",
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Resolve the path to the vendored models.dev cache. The UI copy is canonical.
|
|
92
|
+
* We treat this as best-effort: if the file is missing (developer ran the
|
|
93
|
+
* server without `ui/` checked out), we log and continue with manual rates
|
|
94
|
+
* only — better than crashing the boot.
|
|
95
|
+
*/
|
|
96
|
+
function loadModelsDevCache(): ModelsDevCache | null {
|
|
97
|
+
const candidates = [
|
|
98
|
+
path.join(process.cwd(), "ui", "src", "lib", "modelsdev-cache.json"),
|
|
99
|
+
path.join(process.cwd(), "..", "ui", "src", "lib", "modelsdev-cache.json"),
|
|
100
|
+
];
|
|
101
|
+
for (const cand of candidates) {
|
|
102
|
+
try {
|
|
103
|
+
const raw = readFileSync(cand, "utf-8");
|
|
104
|
+
return JSON.parse(raw) as ModelsDevCache;
|
|
105
|
+
} catch {
|
|
106
|
+
// try next candidate
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface PricingSeedRow {
|
|
113
|
+
provider: PricingProvider;
|
|
114
|
+
model: string;
|
|
115
|
+
tokenClass: PricingTokenClass;
|
|
116
|
+
pricePerMillionUsd: number;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Project a models.dev `cost` block into our pricing-table token classes.
|
|
121
|
+
* Returns one row per non-null cost field.
|
|
122
|
+
*/
|
|
123
|
+
function projectCostBlock(
|
|
124
|
+
provider: PricingProvider,
|
|
125
|
+
model: string,
|
|
126
|
+
cost: ModelsDevCostBlock,
|
|
127
|
+
): PricingSeedRow[] {
|
|
128
|
+
// Phase 2 fix — canonicalize the seed key with the same normalizer the
|
|
129
|
+
// lookup path uses. Idempotent for keys models.dev already serves in
|
|
130
|
+
// canonical form (the common case); also collapses any future drift.
|
|
131
|
+
const key = normalizeModelKey(provider, model);
|
|
132
|
+
const rows: PricingSeedRow[] = [];
|
|
133
|
+
if (typeof cost.input === "number") {
|
|
134
|
+
rows.push({ provider, model: key, tokenClass: "input", pricePerMillionUsd: cost.input });
|
|
135
|
+
}
|
|
136
|
+
if (typeof cost.output === "number") {
|
|
137
|
+
rows.push({ provider, model: key, tokenClass: "output", pricePerMillionUsd: cost.output });
|
|
138
|
+
}
|
|
139
|
+
if (typeof cost.cache_read === "number") {
|
|
140
|
+
rows.push({
|
|
141
|
+
provider,
|
|
142
|
+
model: key,
|
|
143
|
+
tokenClass: "cached_input",
|
|
144
|
+
pricePerMillionUsd: cost.cache_read,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (typeof cost.cache_write === "number") {
|
|
148
|
+
rows.push({
|
|
149
|
+
provider,
|
|
150
|
+
model: key,
|
|
151
|
+
tokenClass: "cache_write",
|
|
152
|
+
pricePerMillionUsd: cost.cache_write,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return rows;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Build the full set of seed rows from a loaded models.dev cache.
|
|
160
|
+
*
|
|
161
|
+
* The mapping logic is intentionally per-provider so the matrix between
|
|
162
|
+
* "what the adapter writes for `model`" and "what models.dev keys by" is
|
|
163
|
+
* explicit and auditable.
|
|
164
|
+
*/
|
|
165
|
+
function buildModelsDevSeedRows(cache: ModelsDevCache): PricingSeedRow[] {
|
|
166
|
+
const rows: PricingSeedRow[] = [];
|
|
167
|
+
|
|
168
|
+
// ---- Anthropic / claude family ----------------------------------------
|
|
169
|
+
// The 'claude' provider (local-CLI adapter) reports the model id as the
|
|
170
|
+
// Anthropic CLI returns it. The 'claude-managed' provider may report
|
|
171
|
+
// either a dated full id or a non-dated id. We project both keyed forms
|
|
172
|
+
// for each model so the recompute path resolves either way.
|
|
173
|
+
const anthropic = cache.anthropic?.models ?? {};
|
|
174
|
+
for (const [id, model] of Object.entries(anthropic)) {
|
|
175
|
+
if (!model?.cost) continue;
|
|
176
|
+
for (const provider of ["claude", "claude-managed"] as const) {
|
|
177
|
+
for (const row of projectCostBlock(provider, id, model.cost)) {
|
|
178
|
+
rows.push(row);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// Anthropic shortnames (opus/sonnet/haiku) → resolve to the current default.
|
|
183
|
+
for (const [shortname, fullId] of Object.entries(ANTHROPIC_SHORTNAME_TO_MODELSDEV)) {
|
|
184
|
+
const target = anthropic[fullId];
|
|
185
|
+
if (!target?.cost) continue;
|
|
186
|
+
for (const provider of ["claude", "claude-managed"] as const) {
|
|
187
|
+
for (const row of projectCostBlock(provider, shortname, target.cost)) {
|
|
188
|
+
rows.push(row);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Pi-mono uses anthropic models via OpenRouter mirrors; project those too.
|
|
193
|
+
for (const [shortname, fullId] of Object.entries(ANTHROPIC_SHORTNAME_TO_MODELSDEV)) {
|
|
194
|
+
const target = anthropic[fullId];
|
|
195
|
+
if (!target?.cost) continue;
|
|
196
|
+
for (const row of projectCostBlock("pi", shortname, target.cost)) {
|
|
197
|
+
rows.push(row);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ---- OpenAI / codex family --------------------------------------------
|
|
202
|
+
const openai = cache.openai?.models ?? {};
|
|
203
|
+
for (const [id, model] of Object.entries(openai)) {
|
|
204
|
+
if (!model?.cost) continue;
|
|
205
|
+
for (const row of projectCostBlock("codex", id, model.cost)) {
|
|
206
|
+
rows.push(row);
|
|
207
|
+
}
|
|
208
|
+
// Phase 2 fix — pi-mono can route to openai models through the
|
|
209
|
+
// github-copilot proxy (`github-copilot/gpt-5.4`). The lookup helper
|
|
210
|
+
// strips the prefix, so we seed the bare id under `pi` too. Without this
|
|
211
|
+
// every gh-copilot-backed pi run fell through to `costSource='unpriced'`.
|
|
212
|
+
for (const row of projectCostBlock("pi", id, model.cost)) {
|
|
213
|
+
rows.push(row);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ---- OpenRouter passthrough (covers gemini + every opencode-routed model)
|
|
218
|
+
const openrouter = cache.openrouter?.models ?? {};
|
|
219
|
+
for (const [id, model] of Object.entries(openrouter)) {
|
|
220
|
+
if (!model?.cost) continue;
|
|
221
|
+
// opencode routes whatever model the user picks; we project them all.
|
|
222
|
+
for (const row of projectCostBlock("opencode", id, model.cost)) {
|
|
223
|
+
rows.push(row);
|
|
224
|
+
}
|
|
225
|
+
// pi-mono also routes via OpenRouter when only OPENROUTER_API_KEY is set
|
|
226
|
+
// (see src/providers/pi-mono-adapter.ts). Without this projection, pi runs
|
|
227
|
+
// against non-anthropic models (e.g. deepseek/deepseek-v4-flash) fall
|
|
228
|
+
// through to costSource='unpriced' even though the model is in the
|
|
229
|
+
// models.dev snapshot.
|
|
230
|
+
for (const row of projectCostBlock("pi", id, model.cost)) {
|
|
231
|
+
rows.push(row);
|
|
232
|
+
}
|
|
233
|
+
// Gemini specifically: also project under the 'gemini' provider so
|
|
234
|
+
// internal-ai callers that tag with provider='gemini' find a hit.
|
|
235
|
+
if (id.startsWith("google/")) {
|
|
236
|
+
const geminiKey = id.replace(/^google\//, "");
|
|
237
|
+
for (const row of projectCostBlock("gemini", geminiKey, model.cost)) {
|
|
238
|
+
rows.push(row);
|
|
239
|
+
}
|
|
240
|
+
// Also store under the full openrouter id so the same row resolves
|
|
241
|
+
// whether the caller passes "google/..." or the stripped name.
|
|
242
|
+
for (const row of projectCostBlock("gemini", id, model.cost)) {
|
|
243
|
+
rows.push(row);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return rows;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Phase 2 entrypoint. Idempotent — safe to call on every boot. Logs a one-line
|
|
253
|
+
* summary so operators can tell whether the boot picked up new rates.
|
|
254
|
+
*/
|
|
255
|
+
export function seedPricingFromModelsDev(opts?: { quiet?: boolean }): {
|
|
256
|
+
inserted: number;
|
|
257
|
+
modelsdevFound: boolean;
|
|
258
|
+
} {
|
|
259
|
+
const db = getDb();
|
|
260
|
+
const cache = loadModelsDevCache();
|
|
261
|
+
const modelsdevRows = cache ? buildModelsDevSeedRows(cache) : [];
|
|
262
|
+
const manualRows = MANUAL_PRICING_OVERRIDES.map((o) => ({
|
|
263
|
+
provider: o.provider,
|
|
264
|
+
model: o.model,
|
|
265
|
+
tokenClass: o.tokenClass,
|
|
266
|
+
pricePerMillionUsd: o.pricePerMillionUsd,
|
|
267
|
+
}));
|
|
268
|
+
const allRows = [...modelsdevRows, ...manualRows];
|
|
269
|
+
|
|
270
|
+
const insert = db.prepare<null, [string, string, string, number]>(
|
|
271
|
+
`INSERT OR IGNORE INTO pricing
|
|
272
|
+
(provider, model, token_class, effective_from, price_per_million_usd, createdAt, lastUpdatedAt)
|
|
273
|
+
VALUES (?, ?, ?, 0, ?, 0, 0)`,
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
let inserted = 0;
|
|
277
|
+
const tx = db.transaction((rows: PricingSeedRow[]) => {
|
|
278
|
+
for (const row of rows) {
|
|
279
|
+
const result = insert.run(row.provider, row.model, row.tokenClass, row.pricePerMillionUsd);
|
|
280
|
+
if (result.changes > 0) inserted += 1;
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
tx(allRows);
|
|
284
|
+
|
|
285
|
+
if (!opts?.quiet) {
|
|
286
|
+
console.log(
|
|
287
|
+
`[pricing] seed: ${inserted} new row(s); ${allRows.length} candidate(s); modelsdev=${
|
|
288
|
+
cache ? "loaded" : "missing"
|
|
289
|
+
}`,
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
return { inserted, modelsdevFound: !!cache };
|
|
293
|
+
}
|
package/src/cli.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import { runLead } from "./commands/lead.ts";
|
|
|
9
9
|
import { Onboard } from "./commands/onboard.tsx";
|
|
10
10
|
import { Setup as Connect } from "./commands/setup.tsx";
|
|
11
11
|
import { runWorker } from "./commands/worker.ts";
|
|
12
|
+
import { getApiKey, setApiKey } from "./utils/api-key.ts";
|
|
12
13
|
|
|
13
14
|
// Get CLI name from bin field (assumes single key)
|
|
14
15
|
const binName = Object.keys(pkg.bin)[0];
|
|
@@ -43,7 +44,7 @@ interface ParsedArgs {
|
|
|
43
44
|
function parseArgs(args: string[]): ParsedArgs {
|
|
44
45
|
const command = args[0] && !args[0].startsWith("-") ? args[0] : undefined;
|
|
45
46
|
let port = process.env.PORT || "3013";
|
|
46
|
-
let key =
|
|
47
|
+
let key = getApiKey();
|
|
47
48
|
let msg = "";
|
|
48
49
|
let headless = false;
|
|
49
50
|
let dryRun = false;
|
|
@@ -151,7 +152,7 @@ const COMMAND_HELP: Record<
|
|
|
151
152
|
connect: {
|
|
152
153
|
usage: `${binName} connect [options]`,
|
|
153
154
|
description:
|
|
154
|
-
"Connect this project to an existing swarm.\nCreates .mcp.json and .claude/settings.local.json with server URL and API key.\nAuto-reads API_KEY from .env if present.",
|
|
155
|
+
"Connect this project to an existing swarm.\nCreates .mcp.json and .claude/settings.local.json with server URL and API key.\nAuto-reads AGENT_SWARM_API_KEY (or legacy API_KEY) from .env if present.",
|
|
155
156
|
options: [
|
|
156
157
|
" --dry-run Show what would be changed without writing",
|
|
157
158
|
" --restore Restore files from .bak backups",
|
|
@@ -248,13 +249,19 @@ const COMMAND_HELP: Record<
|
|
|
248
249
|
options: " -h, --help Show this help",
|
|
249
250
|
examples: [` ${binName} artifact serve`, ` ${binName} artifact help`].join("\n"),
|
|
250
251
|
},
|
|
252
|
+
scripts: {
|
|
253
|
+
usage: `${binName} scripts reembed`,
|
|
254
|
+
description: "Maintenance commands for reusable swarm scripts.",
|
|
255
|
+
options: " -h, --help Show this help",
|
|
256
|
+
examples: ` ${binName} scripts reembed`,
|
|
257
|
+
},
|
|
251
258
|
"codex-login": {
|
|
252
259
|
usage: `${binName} codex-login [options]`,
|
|
253
260
|
description:
|
|
254
261
|
"Authenticate Codex via ChatGPT OAuth (browser or manual paste).\nPrompts interactively for the target API URL and a best-effort masked API key, then stores credentials in the swarm API config store for deployed workers.",
|
|
255
262
|
options: [
|
|
256
263
|
" --api-url <url> Swarm API URL (default: MCP_BASE_URL or http://localhost:3013)",
|
|
257
|
-
" --api-key <key> Swarm API key (default:
|
|
264
|
+
" --api-key <key> Swarm API key (default: AGENT_SWARM_API_KEY or API_KEY, falling back to 123123)",
|
|
258
265
|
" -h, --help Show this help",
|
|
259
266
|
].join("\n"),
|
|
260
267
|
examples: [
|
|
@@ -269,7 +276,7 @@ const COMMAND_HELP: Record<
|
|
|
269
276
|
"Bootstrap Anthropic Managed Agents for the swarm: create the cloud environment, upload plugin/commands/*.md skills, create the managed agent, and persist the resulting IDs to swarm_config so deployed workers restore them at boot. Prompts interactively for ANTHROPIC_API_KEY when not set in env. Idempotent — re-run with --force to recreate.",
|
|
270
277
|
options: [
|
|
271
278
|
" --api-url <url> Swarm API URL (default: MCP_BASE_URL or http://localhost:3013)",
|
|
272
|
-
" --api-key <key> Swarm API key (default:
|
|
279
|
+
" --api-key <key> Swarm API key (default: AGENT_SWARM_API_KEY or API_KEY, falling back to 123123)",
|
|
273
280
|
" --force Recreate Anthropic-side resources even if already configured",
|
|
274
281
|
" -h, --help Show this help",
|
|
275
282
|
].join("\n"),
|
|
@@ -306,6 +313,7 @@ function printHelp(command?: string) {
|
|
|
306
313
|
["claude", "Run Claude CLI"],
|
|
307
314
|
["hook", "Handle Claude Code hook events (stdin)"],
|
|
308
315
|
["artifact", "Manage agent artifacts"],
|
|
316
|
+
["scripts", "Reusable scripts maintenance"],
|
|
309
317
|
["docs", "Open documentation (--open to launch in browser)"],
|
|
310
318
|
["codex-login", "Authenticate Codex via ChatGPT OAuth"],
|
|
311
319
|
["claude-managed-setup", "Bootstrap Anthropic Managed Agents (agent + env + skills)"],
|
|
@@ -324,7 +332,7 @@ function McpServer({ port, apiKey, dbPath }: { port: string; apiKey: string; dbP
|
|
|
324
332
|
|
|
325
333
|
useEffect(() => {
|
|
326
334
|
process.env.PORT = port;
|
|
327
|
-
|
|
335
|
+
setApiKey(apiKey);
|
|
328
336
|
if (dbPath) {
|
|
329
337
|
process.env.DATABASE_PATH = dbPath;
|
|
330
338
|
}
|
|
@@ -547,6 +555,15 @@ if (args.showHelp || args.command === "help" || args.command === undefined) {
|
|
|
547
555
|
port: args.port,
|
|
548
556
|
key: args.key,
|
|
549
557
|
});
|
|
558
|
+
} else if (args.command === "scripts") {
|
|
559
|
+
const scriptsArgs = process.argv.slice(process.argv.indexOf("scripts") + 1);
|
|
560
|
+
if (args.showHelp || scriptsArgs[0] !== "reembed") {
|
|
561
|
+
printHelp("scripts");
|
|
562
|
+
process.exit(scriptsArgs[0] === "reembed" || args.showHelp ? 0 : 1);
|
|
563
|
+
}
|
|
564
|
+
const { runScriptsMaintenanceCommand } = await import("./be/scripts/maintenance");
|
|
565
|
+
await runScriptsMaintenanceCommand(scriptsArgs);
|
|
566
|
+
console.log("Scripts re-embedded.");
|
|
550
567
|
} else if (args.command === "codex-login") {
|
|
551
568
|
const { runCodexLogin } = await import("./commands/codex-login");
|
|
552
569
|
const codexLoginArgs = process.argv.slice(process.argv.indexOf("codex-login") + 1);
|
package/src/commands/artifact.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import { createArtifactServer } from "../artifact-sdk";
|
|
3
|
+
import { getApiKey } from "../utils/api-key";
|
|
3
4
|
|
|
4
5
|
interface ArtifactArgs {
|
|
5
6
|
additionalArgs: string[];
|
|
@@ -137,7 +138,7 @@ async function artifactServe(args: ArtifactArgs) {
|
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
async function artifactList() {
|
|
140
|
-
const apiKey =
|
|
141
|
+
const apiKey = getApiKey();
|
|
141
142
|
const mcpBaseUrl = process.env.MCP_BASE_URL || `http://localhost:${process.env.PORT || "3013"}`;
|
|
142
143
|
const agentId = process.env.AGENT_ID || "";
|
|
143
144
|
|
|
@@ -195,7 +196,7 @@ async function artifactStop(args: ArtifactArgs) {
|
|
|
195
196
|
process.exit(1);
|
|
196
197
|
}
|
|
197
198
|
|
|
198
|
-
const apiKey =
|
|
199
|
+
const apiKey = getApiKey();
|
|
199
200
|
const mcpBaseUrl = process.env.MCP_BASE_URL || `http://localhost:${process.env.PORT || "3013"}`;
|
|
200
201
|
const agentId = process.env.AGENT_ID || "";
|
|
201
202
|
|
|
@@ -33,6 +33,7 @@ import type { BetaEnvironment } from "@anthropic-ai/sdk/resources/beta/environme
|
|
|
33
33
|
import type { SkillCreateResponse } from "@anthropic-ai/sdk/resources/beta/skills";
|
|
34
34
|
import { toFile } from "@anthropic-ai/sdk/uploads";
|
|
35
35
|
|
|
36
|
+
import { getApiKey } from "../utils/api-key";
|
|
36
37
|
import { promptHiddenInput } from "./codex-login.js";
|
|
37
38
|
|
|
38
39
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
@@ -397,7 +398,7 @@ export async function resolveClaudeManagedSetupConfig(
|
|
|
397
398
|
const isInteractive = deps.isInteractive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
398
399
|
|
|
399
400
|
const apiUrl = parsed.apiUrl ?? env.MCP_BASE_URL ?? "http://localhost:3013";
|
|
400
|
-
const apiKey = parsed.apiKey ?? env
|
|
401
|
+
const apiKey = parsed.apiKey ?? (getApiKey(env) || "123123");
|
|
401
402
|
|
|
402
403
|
let anthropicApiKey = env.ANTHROPIC_API_KEY ?? "";
|
|
403
404
|
if (!anthropicApiKey && isInteractive) {
|
|
@@ -553,12 +554,28 @@ export async function runClaudeManagedSetupFlow(
|
|
|
553
554
|
system: mcpServer
|
|
554
555
|
? "You are an agent-swarm worker. Per-task instructions arrive in the next user message. Use the agent-swarm MCP server for swarm operations."
|
|
555
556
|
: "You are an agent-swarm worker. Per-task instructions arrive in the next user message. (No MCP tools available in this configuration.)",
|
|
557
|
+
// Headless workers can't satisfy interactive approval prompts — the
|
|
558
|
+
// Anthropic console parks tool calls in `awaiting approval` and the
|
|
559
|
+
// session stalls. Apply `always_allow` to both toolsets so the sandbox
|
|
560
|
+
// executes tool calls (incl. swarm MCP `store-progress`) without HITL.
|
|
556
561
|
tools: mcpServer
|
|
557
562
|
? [
|
|
558
|
-
{
|
|
559
|
-
|
|
563
|
+
{
|
|
564
|
+
type: "agent_toolset_20260401",
|
|
565
|
+
default_config: { permission_policy: { type: "always_allow" } },
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
type: "mcp_toolset",
|
|
569
|
+
mcp_server_name: mcpServer.name,
|
|
570
|
+
default_config: { permission_policy: { type: "always_allow" } },
|
|
571
|
+
},
|
|
560
572
|
]
|
|
561
|
-
: [
|
|
573
|
+
: [
|
|
574
|
+
{
|
|
575
|
+
type: "agent_toolset_20260401",
|
|
576
|
+
default_config: { permission_policy: { type: "always_allow" } },
|
|
577
|
+
},
|
|
578
|
+
],
|
|
562
579
|
skills: skillsParam,
|
|
563
580
|
...(mcpServer ? { mcp_servers: [mcpServer] } : {}),
|
|
564
581
|
};
|
|
@@ -14,6 +14,7 @@ import { emitKeypressEvents } from "node:readline";
|
|
|
14
14
|
|
|
15
15
|
import { loginCodexOAuth } from "../providers/codex-oauth/flow.js";
|
|
16
16
|
import { storeCodexOAuth } from "../providers/codex-oauth/storage.js";
|
|
17
|
+
import { getApiKey } from "../utils/api-key";
|
|
17
18
|
|
|
18
19
|
type PromptTextFn = (label: string, defaultValue: string) => Promise<string>;
|
|
19
20
|
type PromptSecretFn = (label: string, defaultValue: string, helpText?: string) => Promise<string>;
|
|
@@ -146,7 +147,8 @@ export async function resolveCodexLoginConfig(
|
|
|
146
147
|
const promptSecret = deps.promptSecret ?? promptHiddenInput;
|
|
147
148
|
const isInteractive = deps.isInteractive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
148
149
|
const defaultApiUrl = env.MCP_BASE_URL || "http://localhost:3013";
|
|
149
|
-
const
|
|
150
|
+
const envApiKey = getApiKey(env);
|
|
151
|
+
const defaultApiKey = envApiKey || "123123";
|
|
150
152
|
|
|
151
153
|
let apiUrl = parsed.apiUrl ?? defaultApiUrl;
|
|
152
154
|
let apiKey = parsed.apiKey ?? defaultApiKey;
|
|
@@ -156,8 +158,8 @@ export async function resolveCodexLoginConfig(
|
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
if (!parsed.apiKey && isInteractive) {
|
|
159
|
-
const apiKeyHelp =
|
|
160
|
-
? "Press Enter to use API_KEY from the environment"
|
|
161
|
+
const apiKeyHelp = envApiKey
|
|
162
|
+
? "Press Enter to use AGENT_SWARM_API_KEY/API_KEY from the environment"
|
|
161
163
|
: "Press Enter to use the default local API key";
|
|
162
164
|
apiKey =
|
|
163
165
|
(await promptSecret("Swarm API key", defaultApiKey, apiKeyHelp)).trim() || defaultApiKey;
|