@desplega.ai/agent-swarm 1.74.3 → 1.74.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/openapi.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"openapi": "3.1.0",
|
|
3
3
|
"info": {
|
|
4
4
|
"title": "Agent Swarm API",
|
|
5
|
-
"version": "1.74.
|
|
5
|
+
"version": "1.74.4",
|
|
6
6
|
"description": "Multi-agent orchestration API for Claude Code, Codex, and Gemini CLI. Enables task distribution, agent communication, and service discovery.\n\nMCP tools are documented separately in [MCP.md](./MCP.md)."
|
|
7
7
|
},
|
|
8
8
|
"servers": [
|
package/package.json
CHANGED
|
@@ -66,13 +66,7 @@ import {
|
|
|
66
66
|
} from "@openai/codex-sdk";
|
|
67
67
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
68
68
|
import { type CodexAgentsMdHandle, writeCodexAgentsMd } from "./codex-agents-md";
|
|
69
|
-
import {
|
|
70
|
-
CODEX_DEFAULT_MODEL,
|
|
71
|
-
type CodexModel,
|
|
72
|
-
computeCodexCostUsd,
|
|
73
|
-
getCodexContextWindow,
|
|
74
|
-
resolveCodexModel,
|
|
75
|
-
} from "./codex-models";
|
|
69
|
+
import { computeCodexCostUsd, getCodexContextWindow, resolveCodexModel } from "./codex-models";
|
|
76
70
|
import { credentialsToAuthJson } from "./codex-oauth/auth-json.js";
|
|
77
71
|
import { getValidCodexOAuth } from "./codex-oauth/storage.js";
|
|
78
72
|
import { resolveCodexPrompt } from "./codex-skill-resolver";
|
|
@@ -111,6 +105,68 @@ interface InstalledMcpServersResponse {
|
|
|
111
105
|
total?: number;
|
|
112
106
|
}
|
|
113
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Resolve which Codex auth mode is active for the spawned subprocess and,
|
|
110
|
+
* if needed, restore ChatGPT OAuth credentials from the swarm config store
|
|
111
|
+
* to `~/.codex/auth.json`.
|
|
112
|
+
*
|
|
113
|
+
* Precedence (matches `docker-entrypoint.sh`): `codex_oauth` from the swarm
|
|
114
|
+
* config store > `OPENAI_API_KEY` env var. If both exist, OAuth wins — and
|
|
115
|
+
* if a stale api-key-mode `auth.json` is present, it gets overwritten with
|
|
116
|
+
* the OAuth payload.
|
|
117
|
+
*
|
|
118
|
+
* Returns the `auth_mode` value the spawned Codex CLI will see, or `null`
|
|
119
|
+
* if no `auth.json` exists (Codex will then fall back to `OPENAI_API_KEY`).
|
|
120
|
+
*/
|
|
121
|
+
async function resolveCodexAuthMode(
|
|
122
|
+
config: ProviderSessionConfig,
|
|
123
|
+
emit: (event: ProviderEvent) => void,
|
|
124
|
+
): Promise<string | null> {
|
|
125
|
+
const fs = await import("node:fs/promises");
|
|
126
|
+
const authJsonPath = join(os.homedir(), ".codex", "auth.json");
|
|
127
|
+
|
|
128
|
+
const readAuthMode = async (): Promise<string | null> => {
|
|
129
|
+
try {
|
|
130
|
+
const raw = await fs.readFile(authJsonPath, "utf-8");
|
|
131
|
+
const parsed = JSON.parse(raw) as { auth_mode?: unknown };
|
|
132
|
+
return typeof parsed.auth_mode === "string" ? parsed.auth_mode : null;
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
let currentMode = await readAuthMode();
|
|
139
|
+
|
|
140
|
+
// If config store creds are available and auth.json is missing or in
|
|
141
|
+
// api-key mode, try to restore/upgrade to OAuth. Don't touch a file that's
|
|
142
|
+
// already in chatgpt mode — `getValidCodexOAuth` refreshes and writes back
|
|
143
|
+
// to the config store on its own when called next time.
|
|
144
|
+
if (config.apiUrl && config.apiKey && currentMode !== "chatgpt") {
|
|
145
|
+
const oauthCreds = await getValidCodexOAuth(config.apiUrl, config.apiKey);
|
|
146
|
+
if (oauthCreds) {
|
|
147
|
+
try {
|
|
148
|
+
const authJson = credentialsToAuthJson(oauthCreds);
|
|
149
|
+
await fs.mkdir(join(os.homedir(), ".codex"), { recursive: true, mode: 0o700 });
|
|
150
|
+
await fs.writeFile(authJsonPath, JSON.stringify(authJson, null, 2), { mode: 0o600 });
|
|
151
|
+
const verb = currentMode === null ? "Restored" : "Upgraded api-key auth.json to";
|
|
152
|
+
emit({
|
|
153
|
+
type: "raw_stderr",
|
|
154
|
+
content: `[codex] ${verb} OAuth credentials from config store\n`,
|
|
155
|
+
});
|
|
156
|
+
currentMode = "chatgpt";
|
|
157
|
+
} catch (err) {
|
|
158
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
159
|
+
emit({
|
|
160
|
+
type: "raw_stderr",
|
|
161
|
+
content: `[codex] Failed to write auth.json: ${message}\n`,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return currentMode;
|
|
168
|
+
}
|
|
169
|
+
|
|
114
170
|
/**
|
|
115
171
|
* Build the per-session Codex config object, which becomes the
|
|
116
172
|
* `config` option to `new Codex({ config })`. This layers on top of the
|
|
@@ -131,7 +187,7 @@ interface InstalledMcpServersResponse {
|
|
|
131
187
|
*/
|
|
132
188
|
export async function buildCodexConfig(
|
|
133
189
|
config: ProviderSessionConfig,
|
|
134
|
-
model:
|
|
190
|
+
model: string,
|
|
135
191
|
emit: (event: ProviderEvent) => void,
|
|
136
192
|
): Promise<CodexConfig> {
|
|
137
193
|
const mcpServers: Record<string, Record<string, unknown>> = {};
|
|
@@ -247,7 +303,7 @@ class CodexSession implements ProviderSession {
|
|
|
247
303
|
private readonly thread: Thread;
|
|
248
304
|
private readonly config: ProviderSessionConfig;
|
|
249
305
|
private readonly agentsMdHandle: CodexAgentsMdHandle;
|
|
250
|
-
private readonly resolvedModel:
|
|
306
|
+
private readonly resolvedModel: string;
|
|
251
307
|
private readonly contextWindow: number;
|
|
252
308
|
private readonly skillsDir: string;
|
|
253
309
|
private readonly listeners: Array<(event: ProviderEvent) => void> = [];
|
|
@@ -273,7 +329,7 @@ class CodexSession implements ProviderSession {
|
|
|
273
329
|
thread: Thread,
|
|
274
330
|
config: ProviderSessionConfig,
|
|
275
331
|
agentsMdHandle: CodexAgentsMdHandle,
|
|
276
|
-
resolvedModel:
|
|
332
|
+
resolvedModel: string,
|
|
277
333
|
initialEvents: ProviderEvent[] = [],
|
|
278
334
|
skillsDir?: string,
|
|
279
335
|
) {
|
|
@@ -763,8 +819,9 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
763
819
|
const agentsMdHandle = await writeCodexAgentsMd(config.cwd, config.systemPrompt);
|
|
764
820
|
|
|
765
821
|
try {
|
|
766
|
-
// Resolve the model once and thread it through.
|
|
767
|
-
//
|
|
822
|
+
// Resolve the model once and thread it through. Claude shortnames map
|
|
823
|
+
// to Codex equivalents; everything else passes through verbatim — the
|
|
824
|
+
// SDK is the source of truth for what's valid.
|
|
768
825
|
const resolvedModel = resolveCodexModel(config.model);
|
|
769
826
|
|
|
770
827
|
// Buffer warnings emitted during config-building so they're not lost
|
|
@@ -776,75 +833,32 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
776
833
|
preSessionEvents.push(event);
|
|
777
834
|
};
|
|
778
835
|
|
|
779
|
-
// Warn (as a buffered event) if the caller passed a model that didn't
|
|
780
|
-
// round-trip through `resolveCodexModel`. This catches typos early.
|
|
781
|
-
if (
|
|
782
|
-
config.model &&
|
|
783
|
-
config.model.toLowerCase() !== resolvedModel &&
|
|
784
|
-
!["opus", "sonnet", "haiku"].includes(config.model.toLowerCase())
|
|
785
|
-
) {
|
|
786
|
-
bufferedEmit({
|
|
787
|
-
type: "raw_stderr",
|
|
788
|
-
content: `[codex] Unknown model "${config.model}" — falling back to ${CODEX_DEFAULT_MODEL}. See src/providers/codex-models.ts for the supported list.\n`,
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
|
|
792
836
|
const mergedConfig = await buildCodexConfig(config, resolvedModel, bufferedEmit);
|
|
793
837
|
|
|
838
|
+
// Auth resolution. `codex_oauth` (in the swarm config store) wins over
|
|
839
|
+
// `OPENAI_API_KEY` so users can keep an OpenAI key set for embeddings
|
|
840
|
+
// without it shadowing their ChatGPT login. The entrypoint already runs
|
|
841
|
+
// this same precedence at boot — this block handles local dev (where
|
|
842
|
+
// the entrypoint didn't run) and any case where auth.json is stale.
|
|
843
|
+
const authMode = await resolveCodexAuthMode(config, bufferedEmit);
|
|
844
|
+
|
|
794
845
|
// `CodexOptions.env` does NOT inherit from `process.env`. Construct a
|
|
795
|
-
// minimal env explicitly so the spawned Codex CLI can
|
|
796
|
-
//
|
|
797
|
-
//
|
|
846
|
+
// minimal env explicitly so the spawned Codex CLI can find its binary
|
|
847
|
+
// (PATH) and HOME (for ~/.codex/auth.json). `OPENAI_API_KEY` is only
|
|
848
|
+
// forwarded when auth.json is NOT in chatgpt mode — otherwise it would
|
|
849
|
+
// override the OAuth login at the Codex CLI layer.
|
|
798
850
|
const env: Record<string, string> = {
|
|
799
851
|
PATH: process.env.PATH ?? "",
|
|
800
852
|
HOME: process.env.HOME ?? "",
|
|
801
|
-
...(
|
|
853
|
+
...(authMode !== "chatgpt" && process.env.OPENAI_API_KEY
|
|
854
|
+
? { OPENAI_API_KEY: process.env.OPENAI_API_KEY }
|
|
855
|
+
: {}),
|
|
802
856
|
...(process.env.NODE_EXTRA_CA_CERTS
|
|
803
857
|
? { NODE_EXTRA_CA_CERTS: process.env.NODE_EXTRA_CA_CERTS }
|
|
804
858
|
: {}),
|
|
805
859
|
...(config.env ?? {}),
|
|
806
860
|
};
|
|
807
861
|
|
|
808
|
-
// OAuth credential resolution: if no OPENAI_API_KEY is set, try to
|
|
809
|
-
// restore or refresh ChatGPT OAuth credentials from the config store.
|
|
810
|
-
// The entrypoint also restores at boot, but this handles cases where
|
|
811
|
-
// the entrypoint didn't run (local dev) or tokens expired mid-session.
|
|
812
|
-
if (!process.env.OPENAI_API_KEY && config.apiUrl && config.apiKey) {
|
|
813
|
-
const authJsonPath = join(os.homedir(), ".codex", "auth.json");
|
|
814
|
-
let hasAuth = false;
|
|
815
|
-
try {
|
|
816
|
-
const fs = await import("node:fs/promises");
|
|
817
|
-
await fs.access(authJsonPath);
|
|
818
|
-
hasAuth = true;
|
|
819
|
-
} catch {
|
|
820
|
-
// auth.json doesn't exist
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
if (!hasAuth) {
|
|
824
|
-
const oauthCreds = await getValidCodexOAuth(config.apiUrl, config.apiKey);
|
|
825
|
-
if (oauthCreds) {
|
|
826
|
-
try {
|
|
827
|
-
const fs = await import("node:fs/promises");
|
|
828
|
-
const authJson = credentialsToAuthJson(oauthCreds);
|
|
829
|
-
await fs.mkdir(join(os.homedir(), ".codex"), { recursive: true, mode: 0o700 });
|
|
830
|
-
await fs.writeFile(authJsonPath, JSON.stringify(authJson, null, 2), {
|
|
831
|
-
mode: 0o600,
|
|
832
|
-
});
|
|
833
|
-
bufferedEmit({
|
|
834
|
-
type: "raw_stderr",
|
|
835
|
-
content: "[codex] Restored OAuth credentials from config store\n",
|
|
836
|
-
});
|
|
837
|
-
} catch (err) {
|
|
838
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
839
|
-
bufferedEmit({
|
|
840
|
-
type: "raw_stderr",
|
|
841
|
-
content: `[codex] Failed to write auth.json: ${message}\n`,
|
|
842
|
-
});
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
|
|
848
862
|
// The SDK's default `findCodexPath()` does `require.resolve("@openai/codex")`
|
|
849
863
|
// from the SDK's own module. When agent-swarm runs as a Bun single-file
|
|
850
864
|
// compiled executable, the bundled SDK can't resolve `@openai/codex` at
|
|
@@ -11,7 +11,12 @@
|
|
|
11
11
|
* pulling in the SDK.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* List of Codex models we know about (drives the onboarding model selector,
|
|
16
|
+
* the pricing table, and the context-window map). The resolver does NOT
|
|
17
|
+
* constrain inputs to this list — it passes unknown strings through to the
|
|
18
|
+
* SDK, so new OpenAI models work without a code change.
|
|
19
|
+
*/
|
|
15
20
|
export const CODEX_MODELS = [
|
|
16
21
|
"gpt-5.4", // default — mainline reasoning model w/ frontier coding
|
|
17
22
|
"gpt-5.4-mini", // faster/cheaper
|
|
@@ -29,25 +34,24 @@ export const CODEX_DEFAULT_MODEL: CodexModel = "gpt-5.4";
|
|
|
29
34
|
* to Codex equivalents. Mirrors `pi-mono-adapter.ts:71-75` shortnames map so
|
|
30
35
|
* a task authored for Claude works unchanged when pointed at a Codex worker.
|
|
31
36
|
*/
|
|
32
|
-
const
|
|
37
|
+
const CLAUDE_SHORTNAMES: Record<string, CodexModel> = {
|
|
33
38
|
opus: "gpt-5.4",
|
|
34
|
-
sonnet: "gpt-5.4
|
|
39
|
+
sonnet: "gpt-5.4",
|
|
35
40
|
haiku: "gpt-5.4-mini",
|
|
36
|
-
// explicit passthrough entries so MODEL_OVERRIDE="gpt-5.4" round-trips
|
|
37
|
-
"gpt-5.4": "gpt-5.4",
|
|
38
|
-
"gpt-5.4-mini": "gpt-5.4-mini",
|
|
39
|
-
"gpt-5.3-codex": "gpt-5.3-codex",
|
|
40
|
-
"gpt-5.2-codex": "gpt-5.2-codex",
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* Resolve
|
|
45
|
-
*
|
|
44
|
+
* Resolve a model string (shortname or full Codex model id) into the literal
|
|
45
|
+
* id we hand to the Codex SDK. Behavior:
|
|
46
|
+
* - empty/undefined → `CODEX_DEFAULT_MODEL`
|
|
47
|
+
* - claude shortname (opus/sonnet/haiku) → mapped Codex id
|
|
48
|
+
* - anything else → passthrough (lowercased), so new OpenAI models work
|
|
49
|
+
* without a code change. The SDK is the source of truth for validity.
|
|
46
50
|
*/
|
|
47
|
-
export function resolveCodexModel(modelStr: string | undefined):
|
|
51
|
+
export function resolveCodexModel(modelStr: string | undefined): string {
|
|
48
52
|
if (!modelStr) return CODEX_DEFAULT_MODEL;
|
|
49
53
|
const normalized = modelStr.toLowerCase();
|
|
50
|
-
return
|
|
54
|
+
return CLAUDE_SHORTNAMES[normalized] ?? normalized;
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
/**
|
|
@@ -65,9 +69,13 @@ export const CODEX_MODEL_CONTEXT_WINDOWS: Record<CodexModel, number> = {
|
|
|
65
69
|
"gpt-5.2-codex": 200_000,
|
|
66
70
|
};
|
|
67
71
|
|
|
68
|
-
/**
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Return the context window in tokens for a given Codex model. Unknown models
|
|
74
|
+
* (passthrough strings) get the 200k default — keeps `context_usage` finite
|
|
75
|
+
* even on a model id we haven't catalogued yet.
|
|
76
|
+
*/
|
|
77
|
+
export function getCodexContextWindow(model: string): number {
|
|
78
|
+
return CODEX_MODEL_CONTEXT_WINDOWS[model as CodexModel] ?? 200_000;
|
|
71
79
|
}
|
|
72
80
|
|
|
73
81
|
/**
|
|
@@ -126,12 +134,12 @@ export const CODEX_MODEL_PRICING: Record<CodexModel, CodexModelPricing> = {
|
|
|
126
134
|
* inflate cost on a typo.
|
|
127
135
|
*/
|
|
128
136
|
export function computeCodexCostUsd(
|
|
129
|
-
model:
|
|
137
|
+
model: string,
|
|
130
138
|
inputTokens: number,
|
|
131
139
|
cachedInputTokens: number,
|
|
132
140
|
outputTokens: number,
|
|
133
141
|
): number {
|
|
134
|
-
const pricing = CODEX_MODEL_PRICING[model];
|
|
142
|
+
const pricing = CODEX_MODEL_PRICING[model as CodexModel];
|
|
135
143
|
if (!pricing) return 0;
|
|
136
144
|
const uncachedInput = Math.max(0, inputTokens - cachedInputTokens);
|
|
137
145
|
const inputCost = (uncachedInput / 1_000_000) * pricing.inputPerMillion;
|
|
@@ -628,8 +628,8 @@ describe("resolveCodexModel", () => {
|
|
|
628
628
|
expect(resolveCodexModel("opus")).toBe("gpt-5.4");
|
|
629
629
|
});
|
|
630
630
|
|
|
631
|
-
test("claude shortname 'sonnet' → gpt-5.4
|
|
632
|
-
expect(resolveCodexModel("sonnet")).toBe("gpt-5.4
|
|
631
|
+
test("claude shortname 'sonnet' → gpt-5.4", () => {
|
|
632
|
+
expect(resolveCodexModel("sonnet")).toBe("gpt-5.4");
|
|
633
633
|
});
|
|
634
634
|
|
|
635
635
|
test("claude shortname 'haiku' → gpt-5.4-mini", () => {
|
|
@@ -652,8 +652,9 @@ describe("resolveCodexModel", () => {
|
|
|
652
652
|
expect(resolveCodexModel("GPT-5.4")).toBe("gpt-5.4");
|
|
653
653
|
});
|
|
654
654
|
|
|
655
|
-
test("unknown model
|
|
656
|
-
expect(resolveCodexModel("
|
|
655
|
+
test("unknown model passes through verbatim (lowercased)", () => {
|
|
656
|
+
expect(resolveCodexModel("gpt-5.5-experimental")).toBe("gpt-5.5-experimental");
|
|
657
|
+
expect(resolveCodexModel("GPT-9-FUTURE")).toBe("gpt-9-future");
|
|
657
658
|
});
|
|
658
659
|
});
|
|
659
660
|
|