stagent 0.9.5 → 0.10.0
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 +5 -42
- package/dist/cli.js +42 -18
- package/docs/.coverage-gaps.json +13 -55
- package/docs/.last-generated +1 -1
- package/docs/features/provider-runtimes.md +4 -0
- package/docs/features/schedules.md +32 -4
- package/docs/features/settings.md +28 -5
- package/docs/features/tables.md +9 -2
- package/docs/features/workflows.md +10 -4
- package/docs/journeys/developer.md +15 -1
- package/docs/journeys/personal-use.md +21 -4
- package/docs/superpowers/plans/2026-04-07-instance-bootstrap.md +1691 -0
- package/docs/superpowers/plans/2026-04-08-schedule-orchestration.md +2983 -0
- package/docs/superpowers/plans/2026-04-11-schedule-maxturns-api-control.md +551 -0
- package/docs/superpowers/plans/2026-04-11-task-create-profile-validation.md +864 -0
- package/docs/superpowers/plans/2026-04-11-task-runtime-stagent-mcp-injection.md +739 -0
- package/docs/superpowers/specs/2026-04-08-chat-sse-resilience-hotfix-design.md +201 -0
- package/docs/superpowers/specs/2026-04-08-schedule-orchestration-design.md +371 -0
- package/docs/superpowers/specs/2026-04-08-swarm-visibility-design.md +213 -0
- package/package.json +3 -2
- package/src/__tests__/instrumentation-smoke.test.ts +15 -0
- package/src/app/analytics/page.tsx +1 -21
- package/src/app/api/chat/conversations/[id]/messages/route.ts +22 -1
- package/src/app/api/diagnostics/chat-streams/route.ts +65 -0
- package/src/app/api/instance/config/route.ts +41 -0
- package/src/app/api/instance/init/route.ts +34 -0
- package/src/app/api/instance/upgrade/check/route.ts +26 -0
- package/src/app/api/instance/upgrade/route.ts +96 -0
- package/src/app/api/instance/upgrade/status/route.ts +35 -0
- package/src/app/api/memory/route.ts +0 -11
- package/src/app/api/notifications/route.ts +4 -2
- package/src/app/api/projects/[id]/route.ts +5 -155
- package/src/app/api/projects/__tests__/delete-project.test.ts +10 -19
- package/src/app/api/schedules/[id]/execute/route.ts +111 -0
- package/src/app/api/schedules/[id]/route.ts +9 -1
- package/src/app/api/schedules/__tests__/execute-route.test.ts +118 -0
- package/src/app/api/schedules/route.ts +3 -12
- package/src/app/api/settings/openai/login/route.ts +22 -0
- package/src/app/api/settings/openai/logout/route.ts +7 -0
- package/src/app/api/settings/openai/route.ts +21 -1
- package/src/app/api/settings/providers/route.ts +35 -8
- package/src/app/api/tables/[id]/enrich/__tests__/route.test.ts +153 -0
- package/src/app/api/tables/[id]/enrich/plan/route.ts +98 -0
- package/src/app/api/tables/[id]/enrich/route.ts +147 -0
- package/src/app/api/tables/[id]/enrich/runs/route.ts +25 -0
- package/src/app/api/tasks/[id]/execute/route.ts +0 -21
- package/src/app/api/workflows/[id]/resume/route.ts +59 -0
- package/src/app/api/workflows/[id]/status/route.ts +22 -8
- package/src/app/api/workspace/context/route.ts +2 -0
- package/src/app/api/workspace/fix-data-dir/route.ts +81 -0
- package/src/app/chat/page.tsx +11 -0
- package/src/app/inbox/page.tsx +12 -5
- package/src/app/layout.tsx +42 -21
- package/src/app/page.tsx +0 -2
- package/src/app/settings/page.tsx +6 -9
- package/src/components/chat/__tests__/chat-session-provider.test.tsx +408 -0
- package/src/components/chat/chat-command-popover.tsx +2 -2
- package/src/components/chat/chat-input.tsx +2 -3
- package/src/components/chat/chat-session-provider.tsx +720 -0
- package/src/components/chat/chat-shell.tsx +92 -401
- package/src/components/instance/__tests__/instance-section.test.tsx +125 -0
- package/src/components/instance/instance-section.tsx +382 -0
- package/src/components/instance/upgrade-badge.tsx +219 -0
- package/src/components/notifications/__tests__/batch-proposal-review.test.tsx +95 -0
- package/src/components/notifications/__tests__/notification-item.test.tsx +106 -0
- package/src/components/notifications/batch-proposal-review.tsx +20 -5
- package/src/components/notifications/inbox-list.tsx +11 -2
- package/src/components/notifications/notification-item.tsx +56 -2
- package/src/components/notifications/pending-approval-host.tsx +56 -37
- package/src/components/schedules/schedule-create-sheet.tsx +19 -1
- package/src/components/schedules/schedule-edit-sheet.tsx +20 -1
- package/src/components/schedules/schedule-form.tsx +31 -0
- package/src/components/settings/__tests__/providers-runtimes-section.test.tsx +149 -0
- package/src/components/settings/auth-method-selector.tsx +19 -4
- package/src/components/settings/auth-status-badge.tsx +28 -3
- package/src/components/settings/openai-chatgpt-auth-control.tsx +278 -0
- package/src/components/settings/openai-runtime-section.tsx +7 -1
- package/src/components/settings/providers-runtimes-section.tsx +138 -19
- package/src/components/shared/app-sidebar.tsx +4 -3
- package/src/components/shared/command-palette.tsx +4 -5
- package/src/components/shared/theme-toggle.tsx +5 -24
- package/src/components/shared/workspace-indicator.tsx +61 -2
- package/src/components/tables/__tests__/table-enrichment-sheet.test.tsx +130 -0
- package/src/components/tables/table-create-sheet.tsx +4 -0
- package/src/components/tables/table-enrichment-runs.tsx +103 -0
- package/src/components/tables/table-enrichment-sheet.tsx +538 -0
- package/src/components/tables/table-spreadsheet.tsx +29 -5
- package/src/components/tables/table-toolbar.tsx +10 -1
- package/src/components/tasks/kanban-board.tsx +1 -0
- package/src/components/tasks/kanban-column.tsx +53 -14
- package/src/components/tasks/task-bento-grid.tsx +19 -0
- package/src/components/tasks/task-card.tsx +26 -3
- package/src/components/tasks/task-chip-bar.tsx +24 -0
- package/src/components/tasks/task-result-renderer.tsx +1 -1
- package/src/components/workflows/delay-step-body.tsx +109 -0
- package/src/components/workflows/hooks/use-workflow-status.ts +50 -0
- package/src/components/workflows/loop-status-view.tsx +1 -1
- package/src/components/workflows/shared/step-result.tsx +78 -0
- package/src/components/workflows/shared/workflow-header.tsx +141 -0
- package/src/components/workflows/shared/workflow-loading-skeleton.tsx +36 -0
- package/src/components/workflows/swarm-dashboard.tsx +2 -15
- package/src/components/workflows/views/loop-pattern-view.tsx +137 -0
- package/src/components/workflows/views/sequence-pattern-view.tsx +511 -0
- package/src/components/workflows/workflow-form-view.tsx +133 -16
- package/src/components/workflows/workflow-status-view.tsx +30 -740
- package/src/instrumentation-node.ts +94 -0
- package/src/instrumentation.ts +4 -48
- package/src/lib/agents/__tests__/claude-agent.test.ts +199 -0
- package/src/lib/agents/__tests__/execution-manager.test.ts +1 -27
- package/src/lib/agents/__tests__/failure-reason.test.ts +68 -0
- package/src/lib/agents/__tests__/learned-context.test.ts +0 -11
- package/src/lib/agents/__tests__/learning-session.test.ts +158 -0
- package/src/lib/agents/__tests__/pattern-extractor.test.ts +48 -0
- package/src/lib/agents/claude-agent.ts +155 -18
- package/src/lib/agents/execution-manager.ts +0 -35
- package/src/lib/agents/learned-context.ts +0 -12
- package/src/lib/agents/learning-session.ts +18 -5
- package/src/lib/agents/profiles/__tests__/registry.test.ts +6 -4
- package/src/lib/agents/profiles/builtins/upgrade-assistant/SKILL.md +70 -0
- package/src/lib/agents/profiles/builtins/upgrade-assistant/profile.yaml +32 -0
- package/src/lib/agents/runtime/__tests__/openai-codex-auth.test.ts +118 -0
- package/src/lib/agents/runtime/codex-app-server-client.ts +11 -5
- package/src/lib/agents/runtime/openai-codex-auth.ts +389 -0
- package/src/lib/agents/runtime/openai-codex.ts +29 -60
- package/src/lib/agents/runtime/types.ts +8 -0
- package/src/lib/book/chapter-mapping.ts +11 -0
- package/src/lib/book/content.ts +10 -0
- package/src/lib/chat/__tests__/active-streams.test.ts +49 -0
- package/src/lib/chat/__tests__/finalize-safety-net.test.ts +139 -0
- package/src/lib/chat/__tests__/reconcile.test.ts +137 -0
- package/src/lib/chat/__tests__/stream-telemetry.test.ts +151 -0
- package/src/lib/chat/active-streams.ts +27 -0
- package/src/lib/chat/codex-engine.ts +16 -17
- package/src/lib/chat/context-builder.ts +5 -3
- package/src/lib/chat/engine.ts +50 -3
- package/src/lib/chat/reconcile.ts +117 -0
- package/src/lib/chat/stagent-tools.ts +1 -0
- package/src/lib/chat/stream-telemetry.ts +132 -0
- package/src/lib/chat/suggested-prompts.ts +28 -1
- package/src/lib/chat/system-prompt.ts +26 -1
- package/src/lib/chat/tool-catalog.ts +2 -1
- package/src/lib/chat/tools/__tests__/enrich-table-tool.test.ts +127 -0
- package/src/lib/chat/tools/__tests__/schedule-tools.test.ts +261 -0
- package/src/lib/chat/tools/__tests__/task-tools.test.ts +352 -0
- package/src/lib/chat/tools/__tests__/workflow-tools-dedup.test.ts +217 -0
- package/src/lib/chat/tools/document-tools.ts +29 -13
- package/src/lib/chat/tools/helpers.ts +39 -0
- package/src/lib/chat/tools/notification-tools.ts +9 -5
- package/src/lib/chat/tools/project-tools.ts +33 -0
- package/src/lib/chat/tools/schedule-tools.ts +44 -11
- package/src/lib/chat/tools/table-tools.ts +71 -0
- package/src/lib/chat/tools/task-tools.ts +84 -20
- package/src/lib/chat/tools/workflow-tools.ts +234 -32
- package/src/lib/constants/settings.ts +8 -18
- package/src/lib/data/__tests__/clear.test.ts +56 -2
- package/src/lib/data/clear.ts +20 -15
- package/src/lib/data/delete-project.ts +171 -0
- package/src/lib/db/__tests__/bootstrap.test.ts +1 -1
- package/src/lib/db/bootstrap.ts +45 -16
- package/src/lib/db/index.ts +5 -0
- package/src/lib/db/migrations/0009_add_app_instances.sql +25 -0
- package/src/lib/db/migrations/0024_add_workflow_resume_at.sql +10 -0
- package/src/lib/db/migrations/0025_drop_app_instances.sql +3 -0
- package/src/lib/db/migrations/0026_drop_license.sql +3 -0
- package/src/lib/db/migrations/meta/_journal.json +21 -0
- package/src/lib/db/schema.ts +68 -23
- package/src/lib/environment/workspace-context.ts +13 -1
- package/src/lib/import/dedup.ts +4 -54
- package/src/lib/instance/__tests__/bootstrap.test.ts +362 -0
- package/src/lib/instance/__tests__/detect.test.ts +115 -0
- package/src/lib/instance/__tests__/fingerprint.test.ts +48 -0
- package/src/lib/instance/__tests__/git-ops.test.ts +95 -0
- package/src/lib/instance/__tests__/settings.test.ts +83 -0
- package/src/lib/instance/__tests__/upgrade-poller.test.ts +131 -0
- package/src/lib/instance/bootstrap.ts +270 -0
- package/src/lib/instance/detect.ts +49 -0
- package/src/lib/instance/fingerprint.ts +78 -0
- package/src/lib/instance/git-ops.ts +95 -0
- package/src/lib/instance/settings.ts +61 -0
- package/src/lib/instance/types.ts +77 -0
- package/src/lib/instance/upgrade-poller.ts +153 -0
- package/src/lib/notifications/__tests__/visibility.test.ts +51 -0
- package/src/lib/notifications/visibility.ts +33 -0
- package/src/lib/schedules/__tests__/collision-check.test.ts +93 -0
- package/src/lib/schedules/__tests__/config.test.ts +62 -0
- package/src/lib/schedules/__tests__/firing-metrics.test.ts +99 -0
- package/src/lib/schedules/__tests__/integration.test.ts +82 -0
- package/src/lib/schedules/__tests__/slot-claim.test.ts +242 -0
- package/src/lib/schedules/__tests__/tick-scheduler.test.ts +102 -0
- package/src/lib/schedules/__tests__/turn-budget.test.ts +228 -0
- package/src/lib/schedules/collision-check.ts +105 -0
- package/src/lib/schedules/config.ts +53 -0
- package/src/lib/schedules/scheduler.ts +232 -13
- package/src/lib/schedules/slot-claim.ts +105 -0
- package/src/lib/settings/__tests__/openai-auth.test.ts +101 -0
- package/src/lib/settings/__tests__/openai-login-manager.test.ts +64 -0
- package/src/lib/settings/__tests__/runtime-setup.test.ts +33 -0
- package/src/lib/settings/openai-auth.ts +105 -10
- package/src/lib/settings/openai-login-manager.ts +260 -0
- package/src/lib/settings/runtime-setup.ts +14 -4
- package/src/lib/tables/__tests__/enrichment-planner.test.ts +124 -0
- package/src/lib/tables/__tests__/enrichment.test.ts +147 -0
- package/src/lib/tables/enrichment-planner.ts +454 -0
- package/src/lib/tables/enrichment.ts +328 -0
- package/src/lib/tables/query-builder.ts +5 -2
- package/src/lib/tables/trigger-evaluator.ts +3 -2
- package/src/lib/theme.ts +71 -0
- package/src/lib/usage/ledger.ts +2 -18
- package/src/lib/util/__tests__/similarity.test.ts +106 -0
- package/src/lib/util/similarity.ts +77 -0
- package/src/lib/utils/format-timestamp.ts +24 -0
- package/src/lib/utils/stagent-paths.ts +12 -0
- package/src/lib/validators/__tests__/blueprint.test.ts +172 -0
- package/src/lib/validators/__tests__/settings.test.ts +10 -0
- package/src/lib/validators/blueprint.ts +70 -9
- package/src/lib/validators/profile.ts +2 -2
- package/src/lib/validators/settings.ts +3 -1
- package/src/lib/workflows/__tests__/delay.test.ts +196 -0
- package/src/lib/workflows/__tests__/engine.test.ts +8 -0
- package/src/lib/workflows/__tests__/loop-executor.test.ts +54 -0
- package/src/lib/workflows/__tests__/post-action.test.ts +108 -0
- package/src/lib/workflows/blueprints/instantiator.ts +22 -1
- package/src/lib/workflows/blueprints/types.ts +10 -2
- package/src/lib/workflows/delay.ts +106 -0
- package/src/lib/workflows/engine.ts +207 -4
- package/src/lib/workflows/loop-executor.ts +349 -24
- package/src/lib/workflows/post-action.ts +91 -0
- package/src/lib/workflows/types.ts +166 -1
- package/src/app/api/license/checkout/route.ts +0 -28
- package/src/app/api/license/portal/route.ts +0 -26
- package/src/app/api/license/route.ts +0 -89
- package/src/app/api/license/usage/route.ts +0 -63
- package/src/app/api/marketplace/browse/route.ts +0 -15
- package/src/app/api/marketplace/import/route.ts +0 -28
- package/src/app/api/marketplace/publish/route.ts +0 -40
- package/src/app/api/onboarding/email/route.ts +0 -53
- package/src/app/api/settings/telemetry/route.ts +0 -14
- package/src/app/api/sync/export/route.ts +0 -54
- package/src/app/api/sync/restore/route.ts +0 -37
- package/src/app/api/sync/sessions/route.ts +0 -24
- package/src/app/auth/callback/route.ts +0 -73
- package/src/app/marketplace/page.tsx +0 -19
- package/src/components/analytics/analytics-gate-card.tsx +0 -101
- package/src/components/marketplace/blueprint-card.tsx +0 -61
- package/src/components/marketplace/marketplace-browser.tsx +0 -131
- package/src/components/onboarding/email-capture-card.tsx +0 -104
- package/src/components/settings/activation-form.tsx +0 -95
- package/src/components/settings/cloud-account-section.tsx +0 -147
- package/src/components/settings/cloud-sync-section.tsx +0 -155
- package/src/components/settings/subscription-section.tsx +0 -410
- package/src/components/settings/telemetry-section.tsx +0 -80
- package/src/components/shared/premium-gate-overlay.tsx +0 -50
- package/src/components/shared/schedule-gate-dialog.tsx +0 -64
- package/src/components/shared/upgrade-banner.tsx +0 -112
- package/src/hooks/use-supabase-auth.ts +0 -79
- package/src/lib/billing/email.ts +0 -54
- package/src/lib/billing/products.ts +0 -80
- package/src/lib/billing/stripe.ts +0 -101
- package/src/lib/cloud/supabase-browser.ts +0 -32
- package/src/lib/cloud/supabase-client.ts +0 -56
- package/src/lib/license/__tests__/features.test.ts +0 -56
- package/src/lib/license/__tests__/key-format.test.ts +0 -88
- package/src/lib/license/__tests__/manager.test.ts +0 -64
- package/src/lib/license/__tests__/tier-limits.test.ts +0 -79
- package/src/lib/license/cloud-validation.ts +0 -60
- package/src/lib/license/features.ts +0 -44
- package/src/lib/license/key-format.ts +0 -101
- package/src/lib/license/limit-check.ts +0 -111
- package/src/lib/license/limit-queries.ts +0 -51
- package/src/lib/license/manager.ts +0 -345
- package/src/lib/license/notifications.ts +0 -59
- package/src/lib/license/tier-limits.ts +0 -71
- package/src/lib/marketplace/marketplace-client.ts +0 -107
- package/src/lib/sync/cloud-sync.ts +0 -235
- package/src/lib/telemetry/conversion-events.ts +0 -71
- package/src/lib/telemetry/queue.ts +0 -122
- package/src/lib/validators/license.ts +0 -33
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
extractPlanTypeFromIdToken,
|
|
5
|
+
readCodexAuthStateFromClient,
|
|
6
|
+
} from "@/lib/agents/runtime/openai-codex-auth";
|
|
7
|
+
|
|
8
|
+
describe("openai codex auth", () => {
|
|
9
|
+
it("keeps a ChatGPT session connected when rate limit decoding fails", async () => {
|
|
10
|
+
const client = {
|
|
11
|
+
request: vi.fn(async (method: string) => {
|
|
12
|
+
if (method === "account/read") {
|
|
13
|
+
return {
|
|
14
|
+
account: {
|
|
15
|
+
type: "chatgpt",
|
|
16
|
+
email: "sehgal.manav@gmail.com",
|
|
17
|
+
planType: "prolite",
|
|
18
|
+
},
|
|
19
|
+
requiresOpenaiAuth: false,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (method === "account/rateLimits/read") {
|
|
24
|
+
throw new Error("unknown variant `prolite`");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
throw new Error(`Unexpected method: ${method}`);
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const state = await readCodexAuthStateFromClient(client as never, {
|
|
32
|
+
refreshToken: true,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(state.connected).toBe(true);
|
|
36
|
+
expect(state.account).toEqual({
|
|
37
|
+
type: "chatgpt",
|
|
38
|
+
email: "sehgal.manav@gmail.com",
|
|
39
|
+
planType: "prolite",
|
|
40
|
+
});
|
|
41
|
+
expect(state.rateLimits).toBeNull();
|
|
42
|
+
expect(state.authMode).toBe("chatgpt");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("recovers the plan type from the rate limit error payload when account/read omits it", async () => {
|
|
46
|
+
const client = {
|
|
47
|
+
request: vi.fn(async (method: string) => {
|
|
48
|
+
if (method === "account/read") {
|
|
49
|
+
return {
|
|
50
|
+
account: {
|
|
51
|
+
type: "chatgpt",
|
|
52
|
+
email: "sehgal.manav@gmail.com",
|
|
53
|
+
planType: null,
|
|
54
|
+
},
|
|
55
|
+
requiresOpenaiAuth: false,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (method === "account/rateLimits/read") {
|
|
60
|
+
throw new Error('Decode error: body={ "plan_type": "prolite" }');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
throw new Error(`Unexpected method: ${method}`);
|
|
64
|
+
}),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const state = await readCodexAuthStateFromClient(client as never);
|
|
68
|
+
|
|
69
|
+
expect(state.connected).toBe(true);
|
|
70
|
+
expect(state.account?.planType).toBe("prolite");
|
|
71
|
+
expect(state.rateLimits).toBeNull();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("treats account/read planType=unknown as missing and recovers the upstream plan", async () => {
|
|
75
|
+
const client = {
|
|
76
|
+
request: vi.fn(async (method: string) => {
|
|
77
|
+
if (method === "account/read") {
|
|
78
|
+
return {
|
|
79
|
+
account: {
|
|
80
|
+
type: "chatgpt",
|
|
81
|
+
email: "sehgal.manav@gmail.com",
|
|
82
|
+
planType: "unknown",
|
|
83
|
+
},
|
|
84
|
+
requiresOpenaiAuth: false,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (method === "account/rateLimits/read") {
|
|
89
|
+
throw new Error('Decode error: body={ "plan_type": "prolite" }');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
throw new Error(`Unexpected method: ${method}`);
|
|
93
|
+
}),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const state = await readCodexAuthStateFromClient(client as never);
|
|
97
|
+
|
|
98
|
+
expect(state.connected).toBe(true);
|
|
99
|
+
expect(state.account?.planType).toBe("prolite");
|
|
100
|
+
expect(state.rateLimits).toBeNull();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("extracts the plan type from the stored id token payload", async () => {
|
|
104
|
+
const payload = Buffer.from(
|
|
105
|
+
JSON.stringify({
|
|
106
|
+
"https://api.openai.com/auth": {
|
|
107
|
+
chatgpt_plan_type: "prolite",
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
)
|
|
111
|
+
.toString("base64")
|
|
112
|
+
.replace(/\+/g, "-")
|
|
113
|
+
.replace(/\//g, "_")
|
|
114
|
+
.replace(/=+$/g, "");
|
|
115
|
+
|
|
116
|
+
expect(extractPlanTypeFromIdToken(`header.${payload}.signature`)).toBe("prolite");
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -24,7 +24,7 @@ interface JsonRpcNotification {
|
|
|
24
24
|
type JsonRpcMessage = JsonRpcRequest | JsonRpcResponse | JsonRpcNotification;
|
|
25
25
|
|
|
26
26
|
export interface CodexAppServerClientOptions {
|
|
27
|
-
env?: Record<string, string>;
|
|
27
|
+
env?: Record<string, string | undefined>;
|
|
28
28
|
cwd?: string;
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -59,15 +59,21 @@ export class CodexAppServerClient {
|
|
|
59
59
|
): Promise<CodexAppServerClient> {
|
|
60
60
|
const port = await reservePort();
|
|
61
61
|
const listenUrl = `ws://127.0.0.1:${port}`;
|
|
62
|
+
const env: NodeJS.ProcessEnv = { ...process.env };
|
|
63
|
+
for (const [key, value] of Object.entries(options.env ?? {})) {
|
|
64
|
+
if (value === undefined) {
|
|
65
|
+
delete env[key];
|
|
66
|
+
} else {
|
|
67
|
+
env[key] = value;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
62
71
|
const child = spawn(
|
|
63
72
|
"codex",
|
|
64
73
|
["app-server", "--listen", listenUrl],
|
|
65
74
|
{
|
|
66
75
|
cwd: options.cwd,
|
|
67
|
-
env
|
|
68
|
-
...process.env,
|
|
69
|
-
...(options.env ?? {}),
|
|
70
|
-
},
|
|
76
|
+
env,
|
|
71
77
|
stdio: ["ignore", "pipe", "pipe"],
|
|
72
78
|
}
|
|
73
79
|
);
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
2
|
+
import { dirname } from "path";
|
|
3
|
+
import { getLaunchCwd } from "@/lib/environment/workspace-context";
|
|
4
|
+
import {
|
|
5
|
+
getOpenAIApiKey,
|
|
6
|
+
getOpenAIAuthSettings,
|
|
7
|
+
updateOpenAIAuthStatus,
|
|
8
|
+
clearOpenAIOAuthStatus,
|
|
9
|
+
updateOpenAIOAuthStatus,
|
|
10
|
+
type OpenAIAccountInfo,
|
|
11
|
+
type OpenAIAuthMode,
|
|
12
|
+
type OpenAIRateLimitInfo,
|
|
13
|
+
type OpenAIRateLimitWindow,
|
|
14
|
+
} from "@/lib/settings/openai-auth";
|
|
15
|
+
import {
|
|
16
|
+
getStagentCodexAuthPath,
|
|
17
|
+
getStagentCodexConfigPath,
|
|
18
|
+
getStagentCodexDir,
|
|
19
|
+
} from "@/lib/utils/stagent-paths";
|
|
20
|
+
import { CodexAppServerClient } from "./codex-app-server-client";
|
|
21
|
+
|
|
22
|
+
const STAGENT_CODEX_CONFIG = `cli_auth_credentials_store = "file"
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
interface AccountReadResult {
|
|
26
|
+
account?: {
|
|
27
|
+
type?: string;
|
|
28
|
+
email?: string | null;
|
|
29
|
+
planType?: string | null;
|
|
30
|
+
} | null;
|
|
31
|
+
requiresOpenaiAuth?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface RateLimitsReadResult {
|
|
35
|
+
rateLimits?: {
|
|
36
|
+
limitId?: string | null;
|
|
37
|
+
limitName?: string | null;
|
|
38
|
+
primary?: RateLimitWindowLike | null;
|
|
39
|
+
secondary?: RateLimitWindowLike | null;
|
|
40
|
+
} | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface RateLimitWindowLike {
|
|
44
|
+
usedPercent?: unknown;
|
|
45
|
+
windowDurationMins?: unknown;
|
|
46
|
+
resetsAt?: unknown;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function parseRateLimitWindow(
|
|
50
|
+
value: RateLimitWindowLike | null | undefined
|
|
51
|
+
): OpenAIRateLimitWindow | null {
|
|
52
|
+
if (!value || typeof value !== "object") return null;
|
|
53
|
+
return {
|
|
54
|
+
usedPercent:
|
|
55
|
+
typeof value.usedPercent === "number" ? value.usedPercent : null,
|
|
56
|
+
windowDurationMins:
|
|
57
|
+
typeof value.windowDurationMins === "number" ? value.windowDurationMins : null,
|
|
58
|
+
resetsAt: typeof value.resetsAt === "number" ? value.resetsAt : null,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseAccountInfo(
|
|
63
|
+
value: AccountReadResult["account"]
|
|
64
|
+
): OpenAIAccountInfo | null {
|
|
65
|
+
if (!value?.type) return null;
|
|
66
|
+
if (
|
|
67
|
+
value.type !== "apiKey" &&
|
|
68
|
+
value.type !== "chatgpt" &&
|
|
69
|
+
value.type !== "chatgptAuthTokens"
|
|
70
|
+
) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
type: value.type,
|
|
76
|
+
email: value.email ?? null,
|
|
77
|
+
planType:
|
|
78
|
+
value.planType && value.planType.toLowerCase() !== "unknown"
|
|
79
|
+
? value.planType
|
|
80
|
+
: null,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function extractPlanTypeFromError(error: unknown): string | null {
|
|
85
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
86
|
+
const match = message.match(/"plan_type"\s*:\s*"([^"]+)"/);
|
|
87
|
+
return match?.[1] ?? null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function decodeBase64Url(value: string): string | null {
|
|
91
|
+
try {
|
|
92
|
+
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
93
|
+
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
|
|
94
|
+
return Buffer.from(padded, "base64").toString("utf8");
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function extractPlanTypeFromIdToken(idToken: string): string | null {
|
|
101
|
+
const [, payload] = idToken.split(".");
|
|
102
|
+
if (!payload) return null;
|
|
103
|
+
|
|
104
|
+
const decoded = decodeBase64Url(payload);
|
|
105
|
+
if (!decoded) return null;
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const parsed = JSON.parse(decoded) as {
|
|
109
|
+
"https://api.openai.com/auth"?: {
|
|
110
|
+
chatgpt_plan_type?: string | null;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
return parsed["https://api.openai.com/auth"]?.chatgpt_plan_type ?? null;
|
|
114
|
+
} catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function readStagentCodexPlanTypeFromAuthFile(): Promise<string | null> {
|
|
120
|
+
try {
|
|
121
|
+
const raw = await readFile(getStagentCodexAuthPath(), "utf8");
|
|
122
|
+
const parsed = JSON.parse(raw) as {
|
|
123
|
+
tokens?: {
|
|
124
|
+
id_token?: string | null;
|
|
125
|
+
} | null;
|
|
126
|
+
};
|
|
127
|
+
const idToken = parsed.tokens?.id_token;
|
|
128
|
+
return idToken ? extractPlanTypeFromIdToken(idToken) : null;
|
|
129
|
+
} catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function ensureCodexHomeConfig() {
|
|
135
|
+
const codexDir = getStagentCodexDir();
|
|
136
|
+
const configPath = getStagentCodexConfigPath();
|
|
137
|
+
|
|
138
|
+
await mkdir(codexDir, { recursive: true });
|
|
139
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
140
|
+
|
|
141
|
+
let current = "";
|
|
142
|
+
try {
|
|
143
|
+
current = await readFile(configPath, "utf8");
|
|
144
|
+
} catch {
|
|
145
|
+
// File will be created below.
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (current.includes('cli_auth_credentials_store = "file"')) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const next = current.trim().length > 0
|
|
153
|
+
? `${current.trimEnd()}\n\n${STAGENT_CODEX_CONFIG}`
|
|
154
|
+
: STAGENT_CODEX_CONFIG;
|
|
155
|
+
await writeFile(configPath, next, "utf8");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export async function buildCodexAuthEnv(
|
|
159
|
+
env?: Record<string, string | undefined>
|
|
160
|
+
): Promise<Record<string, string | undefined>> {
|
|
161
|
+
await ensureCodexHomeConfig();
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
...env,
|
|
165
|
+
CODEX_HOME: getStagentCodexDir(),
|
|
166
|
+
OPENAI_API_KEY: env?.OPENAI_API_KEY,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export async function connectStagentCodexClient(options: {
|
|
171
|
+
cwd?: string;
|
|
172
|
+
env?: Record<string, string | undefined>;
|
|
173
|
+
} = {}) {
|
|
174
|
+
const env = await buildCodexAuthEnv(options.env);
|
|
175
|
+
return CodexAppServerClient.connect({
|
|
176
|
+
cwd: options.cwd ?? getLaunchCwd(),
|
|
177
|
+
env,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export async function initializeCodexClient(client: CodexAppServerClient) {
|
|
182
|
+
await client.request("initialize", {
|
|
183
|
+
clientInfo: {
|
|
184
|
+
name: "Stagent",
|
|
185
|
+
version: "0.1.1",
|
|
186
|
+
},
|
|
187
|
+
capabilities: null,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export async function readCodexAuthStateFromClient(
|
|
192
|
+
client: CodexAppServerClient,
|
|
193
|
+
options: { refreshToken?: boolean } = {}
|
|
194
|
+
) {
|
|
195
|
+
const accountResult = (await client.request("account/read", {
|
|
196
|
+
refreshToken: options.refreshToken ?? false,
|
|
197
|
+
})) as AccountReadResult;
|
|
198
|
+
|
|
199
|
+
const account = parseAccountInfo(accountResult.account ?? null);
|
|
200
|
+
if (account?.type === "chatgpt" && !account.planType) {
|
|
201
|
+
account.planType = await readStagentCodexPlanTypeFromAuthFile();
|
|
202
|
+
}
|
|
203
|
+
let rateLimits: OpenAIRateLimitInfo | null = null;
|
|
204
|
+
if (account?.type === "chatgpt") {
|
|
205
|
+
try {
|
|
206
|
+
const rateLimitResult = (await client.request(
|
|
207
|
+
"account/rateLimits/read"
|
|
208
|
+
)) as RateLimitsReadResult;
|
|
209
|
+
const payload = rateLimitResult.rateLimits ?? null;
|
|
210
|
+
if (payload) {
|
|
211
|
+
rateLimits = {
|
|
212
|
+
limitId: payload.limitId ?? null,
|
|
213
|
+
limitName: payload.limitName ?? null,
|
|
214
|
+
primary: parseRateLimitWindow(payload.primary),
|
|
215
|
+
secondary: parseRateLimitWindow(payload.secondary),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (!account.planType) {
|
|
220
|
+
account.planType = extractPlanTypeFromError(error);
|
|
221
|
+
}
|
|
222
|
+
rateLimits = null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const authMode: OpenAIAuthMode =
|
|
227
|
+
account?.type === "apiKey"
|
|
228
|
+
? "apikey"
|
|
229
|
+
: account?.type === "chatgpt"
|
|
230
|
+
? "chatgpt"
|
|
231
|
+
: account?.type === "chatgptAuthTokens"
|
|
232
|
+
? "chatgptAuthTokens"
|
|
233
|
+
: null;
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
connected: account?.type === "chatgpt",
|
|
237
|
+
account,
|
|
238
|
+
rateLimits,
|
|
239
|
+
requiresOpenaiAuth: Boolean(accountResult.requiresOpenaiAuth),
|
|
240
|
+
authMode,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export type ResolvedOpenAICodexAuthContext =
|
|
245
|
+
| {
|
|
246
|
+
method: "api_key";
|
|
247
|
+
apiKeySource: "db" | "env" | "unknown";
|
|
248
|
+
connect: (cwd?: string) => Promise<CodexAppServerClient>;
|
|
249
|
+
}
|
|
250
|
+
| {
|
|
251
|
+
method: "oauth";
|
|
252
|
+
apiKeySource: "oauth";
|
|
253
|
+
connect: (cwd?: string) => Promise<CodexAppServerClient>;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
export async function resolveOpenAICodexAuthContext(): Promise<ResolvedOpenAICodexAuthContext> {
|
|
257
|
+
const settings = await getOpenAIAuthSettings();
|
|
258
|
+
|
|
259
|
+
if (settings.method === "oauth") {
|
|
260
|
+
if (!settings.oauthConnected) {
|
|
261
|
+
try {
|
|
262
|
+
const state = await readStagentCodexAuthState({ refreshToken: false });
|
|
263
|
+
if (!state.connected) {
|
|
264
|
+
throw new Error("OpenAI ChatGPT sign-in is not configured.");
|
|
265
|
+
}
|
|
266
|
+
} catch {
|
|
267
|
+
throw new Error(
|
|
268
|
+
"OpenAI ChatGPT sign-in is not configured. Sign in from Settings > Providers & Runtimes."
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
method: "oauth",
|
|
275
|
+
apiKeySource: "oauth",
|
|
276
|
+
connect: (cwd?: string) =>
|
|
277
|
+
connectStagentCodexClient({
|
|
278
|
+
cwd,
|
|
279
|
+
env: { OPENAI_API_KEY: undefined },
|
|
280
|
+
}),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const { apiKey, source } = await getOpenAIApiKey();
|
|
285
|
+
if (!apiKey) {
|
|
286
|
+
throw new Error("OpenAI API key is not configured");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
method: "api_key",
|
|
291
|
+
apiKeySource: source,
|
|
292
|
+
connect: (cwd?: string) =>
|
|
293
|
+
connectStagentCodexClient({
|
|
294
|
+
cwd,
|
|
295
|
+
env: { OPENAI_API_KEY: apiKey },
|
|
296
|
+
}),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export async function ensureOpenAICodexClientAuthenticated(
|
|
301
|
+
client: CodexAppServerClient,
|
|
302
|
+
auth: ResolvedOpenAICodexAuthContext
|
|
303
|
+
) {
|
|
304
|
+
await initializeCodexClient(client);
|
|
305
|
+
|
|
306
|
+
if (auth.method === "api_key") {
|
|
307
|
+
const { apiKey } = await getOpenAIApiKey();
|
|
308
|
+
if (!apiKey) {
|
|
309
|
+
throw new Error("OpenAI API key is not configured");
|
|
310
|
+
}
|
|
311
|
+
await client.request("account/login/start", {
|
|
312
|
+
type: "apiKey",
|
|
313
|
+
apiKey,
|
|
314
|
+
});
|
|
315
|
+
await updateOpenAIAuthStatus(auth.apiKeySource);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const state = await readCodexAuthStateFromClient(client, {
|
|
320
|
+
refreshToken: true,
|
|
321
|
+
});
|
|
322
|
+
if (!state.connected || state.account?.type !== "chatgpt") {
|
|
323
|
+
throw new Error(
|
|
324
|
+
"OpenAI ChatGPT sign-in is not configured. Sign in from Settings > Providers & Runtimes."
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
await updateOpenAIOAuthStatus({
|
|
328
|
+
connected: true,
|
|
329
|
+
account: state.account,
|
|
330
|
+
authMode: state.authMode,
|
|
331
|
+
rateLimits: state.rateLimits,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export async function readStagentCodexAuthState(options: {
|
|
336
|
+
refreshToken?: boolean;
|
|
337
|
+
cwd?: string;
|
|
338
|
+
} = {}) {
|
|
339
|
+
let client: CodexAppServerClient | null = null;
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
client = await connectStagentCodexClient({ cwd: options.cwd });
|
|
343
|
+
await initializeCodexClient(client);
|
|
344
|
+
|
|
345
|
+
const state = await readCodexAuthStateFromClient(client, {
|
|
346
|
+
refreshToken: options.refreshToken,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
await updateOpenAIOAuthStatus({
|
|
350
|
+
connected: state.connected,
|
|
351
|
+
account: state.account,
|
|
352
|
+
authMode: state.authMode,
|
|
353
|
+
rateLimits: state.rateLimits,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
return state;
|
|
357
|
+
} catch (error) {
|
|
358
|
+
await clearOpenAIOAuthStatus();
|
|
359
|
+
throw error;
|
|
360
|
+
} finally {
|
|
361
|
+
if (client) {
|
|
362
|
+
await client.close();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export async function logoutStagentCodexAuth() {
|
|
368
|
+
let client: CodexAppServerClient | null = null;
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
client = await connectStagentCodexClient();
|
|
372
|
+
await initializeCodexClient(client);
|
|
373
|
+
await client.request("account/logout");
|
|
374
|
+
} catch {
|
|
375
|
+
// Even if app-server logout fails, clear the isolated credential cache below.
|
|
376
|
+
} finally {
|
|
377
|
+
if (client) {
|
|
378
|
+
await client.close();
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
await rm(getStagentCodexAuthPath(), { force: true });
|
|
384
|
+
} catch {
|
|
385
|
+
// Ignore cleanup failures.
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
await clearOpenAIOAuthStatus();
|
|
389
|
+
}
|
|
@@ -14,14 +14,15 @@ import {
|
|
|
14
14
|
prepareTaskOutputDirectory,
|
|
15
15
|
scanTaskOutputDocuments,
|
|
16
16
|
} from "@/lib/documents/output-scanner";
|
|
17
|
-
import {
|
|
18
|
-
getOpenAIApiKey,
|
|
19
|
-
updateOpenAIAuthStatus,
|
|
20
|
-
} from "@/lib/settings/openai-auth";
|
|
21
17
|
import { isToolAllowed } from "@/lib/settings/permissions";
|
|
22
18
|
import { getRuntimeCatalogEntry } from "./catalog";
|
|
23
19
|
import { getLaunchCwd } from "@/lib/environment/workspace-context";
|
|
24
20
|
import { CodexAppServerClient } from "./codex-app-server-client";
|
|
21
|
+
import {
|
|
22
|
+
ensureOpenAICodexClientAuthenticated,
|
|
23
|
+
readCodexAuthStateFromClient,
|
|
24
|
+
resolveOpenAICodexAuthContext,
|
|
25
|
+
} from "./openai-codex-auth";
|
|
25
26
|
import type {
|
|
26
27
|
AgentRuntimeAdapter,
|
|
27
28
|
RuntimeConnectionResult,
|
|
@@ -585,43 +586,19 @@ async function handleServerRequest(
|
|
|
585
586
|
}
|
|
586
587
|
}
|
|
587
588
|
|
|
588
|
-
async function initializeOpenAIClient(
|
|
589
|
-
client: CodexAppServerClient,
|
|
590
|
-
apiKey: string
|
|
591
|
-
) {
|
|
592
|
-
await client.request("initialize", {
|
|
593
|
-
clientInfo: {
|
|
594
|
-
name: "Stagent",
|
|
595
|
-
version: "0.1.1",
|
|
596
|
-
},
|
|
597
|
-
capabilities: null,
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
await client.request("account/login/start", {
|
|
601
|
-
type: "apiKey",
|
|
602
|
-
apiKey,
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
|
|
606
589
|
async function runAssistTurn({
|
|
607
590
|
prompt,
|
|
608
591
|
developerInstructions,
|
|
609
592
|
cwd,
|
|
610
593
|
}: AssistTurnOptions): Promise<{ text: string; usage: UsageSnapshot }> {
|
|
611
|
-
const
|
|
612
|
-
if (!apiKey) {
|
|
613
|
-
throw new Error("OpenAI API key is not configured");
|
|
614
|
-
}
|
|
594
|
+
const auth = await resolveOpenAICodexAuthContext();
|
|
615
595
|
|
|
616
596
|
let client: CodexAppServerClient | null = null;
|
|
617
597
|
let text = "";
|
|
618
598
|
let usage: UsageSnapshot = {};
|
|
619
599
|
|
|
620
600
|
try {
|
|
621
|
-
client = await
|
|
622
|
-
cwd,
|
|
623
|
-
env: { OPENAI_API_KEY: apiKey },
|
|
624
|
-
});
|
|
601
|
+
client = await auth.connect(cwd);
|
|
625
602
|
|
|
626
603
|
client.onNotification = (notification: JsonRpcLikeNotification) => {
|
|
627
604
|
if (notification.method !== "item/agentMessage/delta") return;
|
|
@@ -631,8 +608,7 @@ async function runAssistTurn({
|
|
|
631
608
|
}
|
|
632
609
|
};
|
|
633
610
|
|
|
634
|
-
await
|
|
635
|
-
await updateOpenAIAuthStatus(source);
|
|
611
|
+
await ensureOpenAICodexClientAuthenticated(client, auth);
|
|
636
612
|
|
|
637
613
|
const threadResponse = (await client.request("thread/start", {
|
|
638
614
|
cwd,
|
|
@@ -706,11 +682,7 @@ async function executeOpenAICodexTask(
|
|
|
706
682
|
): Promise<void> {
|
|
707
683
|
const { task, profileId, instructions, prompt, cwd } =
|
|
708
684
|
await resolveTaskExecutionContext(taskId, options);
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
if (!apiKey) {
|
|
712
|
-
throw new Error("OpenAI API key is not configured");
|
|
713
|
-
}
|
|
685
|
+
const auth = await resolveOpenAICodexAuthContext();
|
|
714
686
|
|
|
715
687
|
const abortController = new AbortController();
|
|
716
688
|
let client: CodexAppServerClient | null = null;
|
|
@@ -729,10 +701,7 @@ async function executeOpenAICodexTask(
|
|
|
729
701
|
};
|
|
730
702
|
|
|
731
703
|
try {
|
|
732
|
-
client = await
|
|
733
|
-
cwd,
|
|
734
|
-
env: { OPENAI_API_KEY: apiKey },
|
|
735
|
-
});
|
|
704
|
+
client = await auth.connect(cwd);
|
|
736
705
|
|
|
737
706
|
client.onProcessError = (error) => {
|
|
738
707
|
if (settled) return;
|
|
@@ -879,8 +848,7 @@ async function executeOpenAICodexTask(
|
|
|
879
848
|
};
|
|
880
849
|
});
|
|
881
850
|
|
|
882
|
-
await
|
|
883
|
-
await updateOpenAIAuthStatus(source);
|
|
851
|
+
await ensureOpenAICodexClientAuthenticated(client, auth);
|
|
884
852
|
|
|
885
853
|
if (threadId) {
|
|
886
854
|
await client.request("thread/resume", {
|
|
@@ -1051,29 +1019,30 @@ async function runOpenAITaskAssist(
|
|
|
1051
1019
|
}
|
|
1052
1020
|
|
|
1053
1021
|
async function testOpenAIConnection(): Promise<RuntimeConnectionResult> {
|
|
1054
|
-
const { apiKey, source } = await getOpenAIApiKey();
|
|
1055
|
-
if (!apiKey) {
|
|
1056
|
-
return {
|
|
1057
|
-
connected: false,
|
|
1058
|
-
apiKeySource: "unknown",
|
|
1059
|
-
error: "OpenAI API key is not configured",
|
|
1060
|
-
};
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
1022
|
let client: CodexAppServerClient | null = null;
|
|
1064
1023
|
try {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1024
|
+
const auth = await resolveOpenAICodexAuthContext();
|
|
1025
|
+
client = await auth.connect(getLaunchCwd());
|
|
1026
|
+
await ensureOpenAICodexClientAuthenticated(client, auth);
|
|
1027
|
+
const accountState = await readCodexAuthStateFromClient(client, {
|
|
1028
|
+
refreshToken: auth.apiKeySource === "oauth",
|
|
1068
1029
|
});
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1030
|
+
|
|
1031
|
+
return {
|
|
1032
|
+
connected: auth.apiKeySource === "oauth" ? accountState.connected : true,
|
|
1033
|
+
apiKeySource: auth.apiKeySource,
|
|
1034
|
+
account: accountState.account,
|
|
1035
|
+
rateLimits: accountState.rateLimits,
|
|
1036
|
+
authMode: accountState.authMode,
|
|
1037
|
+
};
|
|
1073
1038
|
} catch (error) {
|
|
1074
1039
|
return {
|
|
1075
1040
|
connected: false,
|
|
1076
|
-
apiKeySource:
|
|
1041
|
+
apiKeySource:
|
|
1042
|
+
error instanceof Error &&
|
|
1043
|
+
error.message.includes("ChatGPT sign-in is not configured")
|
|
1044
|
+
? "oauth"
|
|
1045
|
+
: "unknown",
|
|
1077
1046
|
error: error instanceof Error ? error.message : String(error),
|
|
1078
1047
|
};
|
|
1079
1048
|
} finally {
|