@nema-project/nema 0.0.0-stage → 0.1.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.
Files changed (114) hide show
  1. package/LICENSE +21 -0
  2. package/dist/chunks/chunk-HEIQINX3.js +825 -0
  3. package/dist/chunks/chunk-WT3XUFWU.js +86 -0
  4. package/dist/claude/adapter.d.ts +19 -0
  5. package/dist/claude/adapter.js +258 -0
  6. package/dist/claude/events.d.ts +29 -0
  7. package/dist/claude/events.js +75 -0
  8. package/dist/claude/launch.d.ts +22 -0
  9. package/dist/claude/launch.js +80 -0
  10. package/dist/claude/token.d.ts +9 -0
  11. package/dist/claude/token.js +87 -0
  12. package/dist/codex/adapter.d.ts +17 -0
  13. package/dist/codex/adapter.js +151 -0
  14. package/dist/codex/events.d.ts +16 -0
  15. package/dist/codex/events.js +47 -0
  16. package/dist/codex/isolation.d.ts +33 -0
  17. package/dist/codex/isolation.js +115 -0
  18. package/dist/config.d.ts +29 -0
  19. package/dist/config.js +75 -0
  20. package/dist/configCommands.d.ts +13 -0
  21. package/dist/configCommands.js +69 -0
  22. package/dist/daemon/cleanup.d.ts +5 -0
  23. package/dist/daemon/cleanup.js +49 -0
  24. package/dist/daemon/connection.d.ts +22 -0
  25. package/dist/daemon/connection.js +166 -0
  26. package/dist/daemon/rpc.d.ts +14 -0
  27. package/dist/daemon/rpc.js +20 -0
  28. package/dist/daemon/runner.d.ts +34 -0
  29. package/dist/daemon/runner.js +94 -0
  30. package/dist/daemon/setup.d.ts +39 -0
  31. package/dist/daemon/setup.js +95 -0
  32. package/dist/direct/adapter.d.ts +30 -0
  33. package/dist/direct/adapter.js +190 -0
  34. package/dist/direct/authCommands.d.ts +54 -0
  35. package/dist/direct/authCommands.js +268 -0
  36. package/dist/direct/authStore.d.ts +120 -0
  37. package/dist/direct/authStore.js +351 -0
  38. package/dist/direct/byok.d.ts +27 -0
  39. package/dist/direct/byok.js +67 -0
  40. package/dist/direct/codexLogin.d.ts +34 -0
  41. package/dist/direct/codexLogin.js +93 -0
  42. package/dist/direct/jwt.d.ts +4 -0
  43. package/dist/direct/jwt.js +22 -0
  44. package/dist/direct/lock.d.ts +16 -0
  45. package/dist/direct/lock.js +134 -0
  46. package/dist/direct/loop.d.ts +33 -0
  47. package/dist/direct/loop.js +110 -0
  48. package/dist/direct/siwc.d.ts +63 -0
  49. package/dist/direct/siwc.js +403 -0
  50. package/dist/direct/tools.d.ts +17 -0
  51. package/dist/direct/tools.js +104 -0
  52. package/dist/direct/transport.d.ts +177 -0
  53. package/dist/direct/transport.js +437 -0
  54. package/dist/doctor.d.ts +28 -0
  55. package/dist/doctor.js +178 -0
  56. package/dist/extensions/handlers.d.ts +2 -0
  57. package/dist/extensions/handlers.js +10 -0
  58. package/dist/extensions/store.d.ts +11 -0
  59. package/dist/extensions/store.js +132 -0
  60. package/dist/github/client.d.ts +47 -0
  61. package/dist/github/client.js +383 -0
  62. package/dist/github/gh.d.ts +56 -0
  63. package/dist/github/gh.js +385 -0
  64. package/dist/index.d.ts +2 -0
  65. package/dist/index.js +144 -0
  66. package/dist/localSettings.d.ts +8 -0
  67. package/dist/localSettings.js +26 -0
  68. package/dist/mcp/server.d.ts +5 -0
  69. package/dist/mcp/server.js +83 -0
  70. package/dist/ownedProcess.d.ts +39 -0
  71. package/dist/ownedProcess.js +229 -0
  72. package/dist/platform.d.ts +10 -0
  73. package/dist/platform.js +60 -0
  74. package/dist/report/commands.d.ts +41 -0
  75. package/dist/report/commands.js +159 -0
  76. package/dist/report/desktop.d.ts +3 -0
  77. package/dist/report/desktop.js +123 -0
  78. package/dist/report/git.d.ts +5 -0
  79. package/dist/report/git.js +42 -0
  80. package/dist/report/install.d.ts +22 -0
  81. package/dist/report/install.js +183 -0
  82. package/dist/report/send.d.ts +35 -0
  83. package/dist/report/send.js +208 -0
  84. package/dist/report/token.d.ts +5 -0
  85. package/dist/report/token.js +86 -0
  86. package/dist/repos/git.d.ts +48 -0
  87. package/dist/repos/git.js +370 -0
  88. package/dist/repos/graphify.d.ts +32 -0
  89. package/dist/repos/graphify.js +292 -0
  90. package/dist/repos/handlers.d.ts +20 -0
  91. package/dist/repos/handlers.js +255 -0
  92. package/dist/repos/indexState.d.ts +10 -0
  93. package/dist/repos/indexState.js +45 -0
  94. package/dist/repos/pickDirectory.d.ts +11 -0
  95. package/dist/repos/pickDirectory.js +140 -0
  96. package/dist/repos/reportCheck.d.ts +13 -0
  97. package/dist/repos/reportCheck.js +268 -0
  98. package/dist/repos/watcher.d.ts +64 -0
  99. package/dist/repos/watcher.js +202 -0
  100. package/dist/room/client.d.ts +26 -0
  101. package/dist/room/client.js +76 -0
  102. package/dist/secrets/file.d.ts +12 -0
  103. package/dist/secrets/file.js +89 -0
  104. package/dist/secrets/github.d.ts +10 -0
  105. package/dist/secrets/github.js +29 -0
  106. package/dist/secrets/permissions.d.ts +7 -0
  107. package/dist/secrets/permissions.js +86 -0
  108. package/dist/systemGit.d.ts +14 -0
  109. package/dist/systemGit.js +75 -0
  110. package/dist/wp/commands.d.ts +15 -0
  111. package/dist/wp/commands.js +103 -0
  112. package/package.json +26 -4
  113. package/src/report/SKILL.md +21 -0
  114. package/README.md +0 -3
@@ -0,0 +1,177 @@
1
+ import type { StoredAuth } from "./authStore.js";
2
+ export type InputTextPart = {
3
+ type: "input_text";
4
+ text: string;
5
+ };
6
+ export type OutputTextPart = {
7
+ type: "output_text";
8
+ text: string;
9
+ annotations?: unknown[];
10
+ };
11
+ export type SummaryTextPart = {
12
+ type: "summary_text";
13
+ text: string;
14
+ };
15
+ /** Typed content parts only: the backend rejects string `content`. */
16
+ export type InputItem = {
17
+ type: "message";
18
+ role: "user" | "developer" | "system";
19
+ content: InputTextPart[];
20
+ } | {
21
+ type: "message";
22
+ role: "assistant";
23
+ content: OutputTextPart[];
24
+ id?: string;
25
+ status?: "completed";
26
+ } | {
27
+ type: "function_call";
28
+ call_id: string;
29
+ name: string;
30
+ arguments: string;
31
+ id?: string;
32
+ } | {
33
+ type: "function_call_output";
34
+ call_id: string;
35
+ output: string;
36
+ } | {
37
+ type: "reasoning";
38
+ summary: SummaryTextPart[];
39
+ encrypted_content?: string | null;
40
+ id?: string;
41
+ };
42
+ export type FunctionTool = {
43
+ type: "function";
44
+ name: string;
45
+ description: string;
46
+ strict: boolean;
47
+ parameters: Record<string, unknown>;
48
+ };
49
+ export type OutputMessage = {
50
+ type: "message";
51
+ role: "assistant";
52
+ content: Array<OutputTextPart | {
53
+ type: string;
54
+ [k: string]: unknown;
55
+ }>;
56
+ id?: string;
57
+ status?: string;
58
+ };
59
+ export type OutputFunctionCall = {
60
+ type: "function_call";
61
+ call_id: string;
62
+ name: string;
63
+ arguments: string;
64
+ id?: string;
65
+ status?: string;
66
+ };
67
+ export type OutputReasoning = {
68
+ type: "reasoning";
69
+ summary?: SummaryTextPart[];
70
+ encrypted_content?: string | null;
71
+ id?: string;
72
+ };
73
+ /** Items are passed through as the server sent them; other item types keep their raw shape. */
74
+ export type OutputItem = OutputMessage | OutputFunctionCall | OutputReasoning | {
75
+ type: string;
76
+ [k: string]: unknown;
77
+ };
78
+ export declare const isFunctionCall: (i: OutputItem) => i is OutputFunctionCall;
79
+ export declare const isMessage: (i: OutputItem) => i is OutputMessage;
80
+ /** Concatenated `output_text` of all message items. */
81
+ export declare const outputText: (items: OutputItem[]) => string;
82
+ export type ResponsesRequest = {
83
+ model: string;
84
+ instructions: string;
85
+ input: InputItem[];
86
+ tools: FunctionTool[];
87
+ reasoning: {
88
+ effort: string;
89
+ summary: "auto";
90
+ };
91
+ sessionId: string;
92
+ };
93
+ export type CollectedResponse = {
94
+ status: "completed" | "incomplete";
95
+ items: OutputItem[];
96
+ incompleteReason?: string;
97
+ };
98
+ /** Anything that can hand the transport a bearer token. AuthStore and CodexFileTokenSource both qualify. */
99
+ export type TokenSource = {
100
+ getAccessToken(opts?: {
101
+ force?: boolean;
102
+ rejectedAccessToken?: string;
103
+ signal?: AbortSignal;
104
+ }): Promise<{
105
+ accessToken: string;
106
+ auth: StoredAuth;
107
+ }>;
108
+ recordAuthError(reason: {
109
+ code: string;
110
+ message: string;
111
+ }, forAuth: StoredAuth): Promise<void>;
112
+ };
113
+ export type TransportProfile = {
114
+ baseUrl: string;
115
+ extraHeaders(auth: StoredAuth): Record<string, string>;
116
+ modelsPath: string;
117
+ /** Whether a 401 should force one token refresh. Defaults to true. */
118
+ refreshOn401?: boolean;
119
+ /** Tried once when modelsPath answers HTTP 400 (Codex catalog: client_version 99.0.0 then 0.0.0). */
120
+ modelsPathFallback?: string;
121
+ parseModels(json: unknown): string[];
122
+ };
123
+ /** [MODELS]: public API base, `GET /models`, keep `visibility === "list"` slugs in server order. */
124
+ export declare const SIWC_PROFILE: TransportProfile;
125
+ export declare const BYOK_PROFILE: TransportProfile;
126
+ /**
127
+ * Hermes Codex route: `POST https://chatgpt.com/backend-api/codex/responses` with the Codex CLI's
128
+ * access token. Hidden catalog rows are dropped; the rest sort by (priority, slug). Identity stays
129
+ * `nema-room` — this does not impersonate the Codex CLI.
130
+ */
131
+ export declare const COMPAT_PROFILE: TransportProfile;
132
+ export type TransportContext = {
133
+ auth: TokenSource;
134
+ profile: TransportProfile;
135
+ /** Nema CLI version for `User-Agent: NemaRoom/<version>`. */
136
+ version: string;
137
+ signal: AbortSignal;
138
+ /** Delay before the single network/5xx retry (default 1000 ms). */
139
+ retryDelayMs?: number;
140
+ };
141
+ /** Codes meaning the plan's usage is exhausted or unavailable (HTTP body or inside response.failed / error). */
142
+ export declare const QUOTA_CODES: readonly ["usage_limit_reached", "usage_limit_exceeded", "subscription_sharing_usage_limit_exceeded", "usage_not_included", "insufficient_quota", "subscription_sharing_usage_unavailable"];
143
+ export declare class ModelQuotaError extends Error {
144
+ readonly status?: number;
145
+ readonly code?: string;
146
+ /** When usage resets, epoch milliseconds — only if the server said so. */
147
+ readonly resetsAt?: number;
148
+ constructor(o: {
149
+ status?: number;
150
+ code?: string;
151
+ message?: string;
152
+ resetsAt?: number;
153
+ });
154
+ }
155
+ export declare class ModelApiError extends Error {
156
+ readonly status?: number;
157
+ readonly code?: string;
158
+ constructor(o: {
159
+ status?: number;
160
+ code?: string;
161
+ message: string;
162
+ }, options?: ErrorOptions);
163
+ }
164
+ export declare class ByokRejectedError extends Error {
165
+ constructor();
166
+ }
167
+ /** The account's listed models. A 400 on modelsPath retries modelsPathFallback once (Codex catalog versions). */
168
+ export declare function listModels(ctx: TransportContext): Promise<string[]>;
169
+ /**
170
+ * One streaming Responses call. Text deltas go to `onTextDelta` as they arrive; the result is assembled from
171
+ * `response.output_item.done` items. Throws ModelQuotaError / ModelApiError / ReloginRequiredError (and the
172
+ * AuthStore's AuthRateLimitedError / TransientAuthError); an abort rejects with the signal's reason.
173
+ * A stream that breaks after it opened is not retried (deltas may already have been delivered).
174
+ */
175
+ export declare function streamResponse(req: ResponsesRequest, ctx: TransportContext & {
176
+ onTextDelta?: (t: string) => void;
177
+ }): Promise<CollectedResponse>;
@@ -0,0 +1,437 @@
1
+ // direct/transport.js
2
+ import { DEFAULT_BYOK_BASE_URL } from "../config.js";
3
+ import { ReloginRequiredError } from "./authStore.js";
4
+ import { codexAccountHeaders } from "./codexLogin.js";
5
+ import { SIWC } from "./siwc.js";
6
+ var isFunctionCall = (i) => i.type === "function_call";
7
+ var isMessage = (i) => i.type === "message";
8
+ var outputText = (items) => items.filter(isMessage).flatMap((m) => Array.isArray(m.content) ? m.content : []).map((p) => p.type === "output_text" && typeof p.text === "string" ? p.text : "").join("");
9
+ var SIWC_PROFILE = {
10
+ baseUrl: SIWC.apiBase,
11
+ extraHeaders: () => ({}),
12
+ modelsPath: "/models",
13
+ parseModels(json) {
14
+ const models = json && typeof json === "object" ? json.models : void 0;
15
+ if (!Array.isArray(models))
16
+ throw new ModelApiError({ message: "\u6A21\u578B\u5217\u8868\u54CD\u5E94\u7F3A\u5C11 models \u6570\u7EC4" });
17
+ return models.filter((m) => !!m && typeof m === "object" && m.visibility === "list").map((m) => m.slug).filter((s) => typeof s === "string" && s !== "");
18
+ }
19
+ };
20
+ var BYOK_PROFILE = {
21
+ baseUrl: DEFAULT_BYOK_BASE_URL,
22
+ extraHeaders: () => ({}),
23
+ modelsPath: "/models",
24
+ refreshOn401: false,
25
+ parseModels: SIWC_PROFILE.parseModels
26
+ };
27
+ var COMPAT_PROFILE = {
28
+ baseUrl: "https://chatgpt.com/backend-api/codex",
29
+ extraHeaders: (auth) => codexAccountHeaders(auth.tokens.access_token),
30
+ modelsPath: "/models?client_version=99.0.0",
31
+ modelsPathFallback: "/models?client_version=0.0.0",
32
+ parseModels(json) {
33
+ const models = json && typeof json === "object" ? json.models : void 0;
34
+ if (!Array.isArray(models))
35
+ throw new ModelApiError({ message: "\u6A21\u578B\u5217\u8868\u54CD\u5E94\u7F3A\u5C11 models \u6570\u7EC4" });
36
+ const rows = [];
37
+ for (const item of models) {
38
+ if (!item || typeof item !== "object")
39
+ continue;
40
+ const m = item;
41
+ if (typeof m.slug !== "string" || m.slug.trim() === "")
42
+ continue;
43
+ const visibility = typeof m.visibility === "string" ? m.visibility.trim().toLowerCase() : "";
44
+ if (visibility === "hide" || visibility === "hidden")
45
+ continue;
46
+ const rank = typeof m.priority === "number" && Number.isFinite(m.priority) ? Math.trunc(m.priority) : 1e4;
47
+ rows.push({ rank, slug: m.slug.trim() });
48
+ }
49
+ rows.sort((a, b) => a.rank - b.rank || (a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0));
50
+ const seen = /* @__PURE__ */ new Set();
51
+ const slugs = [];
52
+ for (const row of rows) {
53
+ if (seen.has(row.slug))
54
+ continue;
55
+ seen.add(row.slug);
56
+ slugs.push(row.slug);
57
+ }
58
+ return slugs;
59
+ }
60
+ };
61
+ var QUOTA_CODES = [
62
+ "usage_limit_reached",
63
+ "usage_limit_exceeded",
64
+ "subscription_sharing_usage_limit_exceeded",
65
+ "usage_not_included",
66
+ "insufficient_quota",
67
+ "subscription_sharing_usage_unavailable"
68
+ ];
69
+ var ModelQuotaError = class extends Error {
70
+ status;
71
+ code;
72
+ /** When usage resets, epoch milliseconds — only if the server said so. */
73
+ resetsAt;
74
+ constructor(o) {
75
+ super(`\u8BA2\u9605\u989D\u5EA6\u4E0D\u53EF\u7528\uFF08${describe(o.status, o.code)}\uFF09${o.message ? `\uFF1A${o.message}` : ""}`);
76
+ this.name = "ModelQuotaError";
77
+ this.status = o.status;
78
+ this.code = o.code;
79
+ this.resetsAt = o.resetsAt;
80
+ }
81
+ };
82
+ var ModelApiError = class extends Error {
83
+ status;
84
+ code;
85
+ constructor(o, options) {
86
+ super(o.message, options);
87
+ this.name = "ModelApiError";
88
+ this.status = o.status;
89
+ this.code = o.code;
90
+ }
91
+ };
92
+ var ByokRejectedError = class extends Error {
93
+ constructor() {
94
+ super("API key \u88AB\u62D2\u7EDD\uFF0C\u8BF7\u5728\u7EC8\u7AEF\u8FD0\u884C nema auth byok");
95
+ this.name = "ByokRejectedError";
96
+ }
97
+ };
98
+ function describe(status, code) {
99
+ return [status !== void 0 ? `HTTP ${status}` : "", code ?? ""].filter(Boolean).join(" ") || "\u672A\u77E5\u9519\u8BEF";
100
+ }
101
+ function parseErrorObject(o, now) {
102
+ if (!o || typeof o !== "object")
103
+ return {};
104
+ const body = o;
105
+ const nested = body.error && typeof body.error === "object" ? body.error : void 0;
106
+ const src = nested ?? body;
107
+ const str = (v) => typeof v === "string" && v !== "" ? v : void 0;
108
+ const code = str(src.code) ?? str(src.type) ?? (typeof body.error === "string" ? body.error : void 0);
109
+ const message = str(src.message) ?? str(body.detail) ?? str(body.message);
110
+ let resetsAt;
111
+ const ra = src.resets_at ?? body.resets_at;
112
+ const rin = src.resets_in_seconds ?? body.resets_in_seconds;
113
+ if (typeof ra === "number" && Number.isFinite(ra) && ra > 0)
114
+ resetsAt = ra > 1e12 ? ra : ra * 1e3;
115
+ else if (typeof rin === "number" && Number.isFinite(rin) && rin >= 0)
116
+ resetsAt = now + rin * 1e3;
117
+ return { code, message, param: str(src.param), resetsAt };
118
+ }
119
+ function toError(p) {
120
+ if (p.code && QUOTA_CODES.includes(p.code)) {
121
+ return new ModelQuotaError({ status: p.status, code: p.code, message: p.message, resetsAt: p.resetsAt });
122
+ }
123
+ const detail = [p.message, p.param ? `param=${p.param}` : "", p.requestId ? `request id ${p.requestId}` : ""].filter(Boolean).join("\uFF1B");
124
+ return new ModelApiError({ status: p.status, code: p.code, message: `${describe(p.status, p.code)}${detail ? `\uFF1A${detail}` : ""}` });
125
+ }
126
+ async function httpError(res) {
127
+ let parsed = {};
128
+ const text = await res.text().catch(() => "");
129
+ try {
130
+ parsed = parseErrorObject(JSON.parse(text), Date.now());
131
+ } catch {
132
+ if (text.trim())
133
+ parsed = { message: text.trim().slice(0, 300) };
134
+ }
135
+ return { ...parsed, status: res.status, requestId: res.headers.get("x-request-id") ?? void 0 };
136
+ }
137
+ var errMessage = (e) => e instanceof Error ? e.message : String(e);
138
+ function connectionMessage(e) {
139
+ const code = e?.cause?.code;
140
+ return typeof code === "string" && /^[A-Z0-9_]+$/.test(code) ? `\u65E0\u6CD5\u8FDE\u63A5\u6A21\u578B\u63A5\u53E3\uFF08${code}\uFF09` : "\u65E0\u6CD5\u8FDE\u63A5\u6A21\u578B\u63A5\u53E3";
141
+ }
142
+ function sleep(ms, signal) {
143
+ if (ms <= 0)
144
+ return Promise.resolve();
145
+ return new Promise((resolve, reject) => {
146
+ const t = setTimeout(() => {
147
+ signal.removeEventListener("abort", onAbort);
148
+ resolve();
149
+ }, ms);
150
+ const onAbort = () => {
151
+ clearTimeout(t);
152
+ reject(signal.reason);
153
+ };
154
+ signal.addEventListener("abort", onAbort, { once: true });
155
+ });
156
+ }
157
+ async function send(ctx, url, init) {
158
+ let { accessToken, auth } = await ctx.auth.getAccessToken({ signal: ctx.signal });
159
+ let authRetried = false;
160
+ let transientRetried = false;
161
+ for (; ; ) {
162
+ ctx.signal.throwIfAborted();
163
+ const headers = {
164
+ ...ctx.profile.extraHeaders(auth),
165
+ Authorization: `Bearer ${accessToken}`,
166
+ originator: SIWC.originator,
167
+ "User-Agent": `NemaRoom/${ctx.version}`
168
+ };
169
+ let res;
170
+ try {
171
+ res = await fetch(url, { ...init(headers), signal: ctx.signal });
172
+ } catch (e) {
173
+ if (ctx.signal.aborted)
174
+ throw e;
175
+ if (!transientRetried) {
176
+ transientRetried = true;
177
+ await sleep(ctx.retryDelayMs ?? 1e3, ctx.signal);
178
+ continue;
179
+ }
180
+ throw new ModelApiError({ message: connectionMessage(e) }, { cause: e });
181
+ }
182
+ if (res.ok)
183
+ return res;
184
+ ctx.signal.throwIfAborted();
185
+ if (res.status === 401) {
186
+ if (ctx.profile.refreshOn401 === false)
187
+ throw new ByokRejectedError();
188
+ const err2 = await httpError(res);
189
+ if (authRetried) {
190
+ await ctx.auth.recordAuthError({ code: "model_unauthorized", message: `\u6A21\u578B\u63A5\u53E3\u4ECD\u8FD4\u56DE 401\uFF08${err2.code ?? "unauthorized"}\uFF09` }, auth);
191
+ throw new ReloginRequiredError(err2.code ?? "unauthorized");
192
+ }
193
+ authRetried = true;
194
+ ({ accessToken, auth } = await ctx.auth.getAccessToken({ force: true, rejectedAccessToken: accessToken, signal: ctx.signal }));
195
+ continue;
196
+ }
197
+ const err = toError(await httpError(res));
198
+ if (err instanceof ModelQuotaError)
199
+ throw err;
200
+ if (res.status >= 500 && !transientRetried) {
201
+ transientRetried = true;
202
+ await sleep(ctx.retryDelayMs ?? 1e3, ctx.signal);
203
+ continue;
204
+ }
205
+ throw err;
206
+ }
207
+ }
208
+ async function listModelsAt(ctx, modelsPath) {
209
+ const res = await send(ctx, `${ctx.profile.baseUrl}${modelsPath}`, (h) => ({ method: "GET", headers: { ...h, Accept: "application/json" } }));
210
+ let body;
211
+ try {
212
+ body = await res.json();
213
+ } catch (e) {
214
+ throw new ModelApiError({ status: res.status, message: `\u6A21\u578B\u5217\u8868\u54CD\u5E94\u4E0D\u662F JSON\uFF1A${errMessage(e)}` }, { cause: e });
215
+ }
216
+ return ctx.profile.parseModels(body);
217
+ }
218
+ async function listModels(ctx) {
219
+ try {
220
+ return await listModelsAt(ctx, ctx.profile.modelsPath);
221
+ } catch (e) {
222
+ if (ctx.profile.modelsPathFallback && e instanceof ModelApiError && e.status === 400) {
223
+ return await listModelsAt(ctx, ctx.profile.modelsPathFallback);
224
+ }
225
+ throw e;
226
+ }
227
+ }
228
+ function requestBody(req) {
229
+ return {
230
+ model: req.model,
231
+ instructions: req.instructions,
232
+ input: req.input,
233
+ store: false,
234
+ stream: true,
235
+ // Omitted when empty (research §3: the backend wants no empty tools array).
236
+ ...req.tools.length > 0 ? { tools: req.tools, tool_choice: "auto", parallel_tool_calls: false } : {},
237
+ reasoning: req.reasoning,
238
+ include: ["reasoning.encrypted_content"]
239
+ };
240
+ }
241
+ var Assembler = class {
242
+ onTextDelta;
243
+ seq = 0;
244
+ done = [];
245
+ pending = [];
246
+ constructor(onTextDelta) {
247
+ this.onTextDelta = onTextDelta;
248
+ }
249
+ findPending(ev) {
250
+ const id = typeof ev.item_id === "string" ? ev.item_id : void 0;
251
+ const idx = typeof ev.output_index === "number" ? ev.output_index : void 0;
252
+ return this.pending.find((p) => !p.done && (id !== void 0 && p.item.id === id || id === void 0 && idx !== void 0 && p.outputIndex === idx));
253
+ }
254
+ /** Returns the final result on a terminal event; throws on failure events. */
255
+ handle(ev) {
256
+ const idx = typeof ev.output_index === "number" ? ev.output_index : void 0;
257
+ switch (ev.type) {
258
+ case "response.output_text.delta":
259
+ if (typeof ev.delta === "string" && ev.delta !== "")
260
+ this.onTextDelta?.(ev.delta);
261
+ return null;
262
+ case "response.output_item.added": {
263
+ const item = ev.item;
264
+ if (item && item.type === "function_call") {
265
+ this.pending.push({ item, outputIndex: idx, seq: this.seq++, args: typeof item.arguments === "string" ? item.arguments : "", done: false });
266
+ }
267
+ return null;
268
+ }
269
+ case "response.function_call_arguments.delta": {
270
+ const p = this.findPending(ev);
271
+ if (p && typeof ev.delta === "string")
272
+ p.args += ev.delta;
273
+ return null;
274
+ }
275
+ case "response.function_call_arguments.done": {
276
+ const p = this.findPending(ev);
277
+ if (p && typeof ev.arguments === "string")
278
+ p.args = ev.arguments;
279
+ return null;
280
+ }
281
+ case "response.output_item.done": {
282
+ const item = ev.item;
283
+ if (!item || typeof item !== "object" || typeof item.type !== "string")
284
+ return null;
285
+ const p = this.pending.find((x) => !x.done && (typeof item.id === "string" && x.item.id === item.id || item.id === void 0 && idx !== void 0 && x.outputIndex === idx));
286
+ if (p)
287
+ p.done = true;
288
+ this.done.push({ item, outputIndex: idx, seq: p?.seq ?? this.seq++ });
289
+ return null;
290
+ }
291
+ case "response.completed":
292
+ case "response.incomplete": {
293
+ const response = ev.response ?? {};
294
+ const items = this.items(response.output);
295
+ if (ev.type === "response.incomplete") {
296
+ const details = response.incomplete_details;
297
+ return { status: "incomplete", items, incompleteReason: typeof details?.reason === "string" ? details.reason : "incomplete" };
298
+ }
299
+ if (!items.some((i) => i.type === "message" || i.type === "function_call")) {
300
+ return { status: "incomplete", items, incompleteReason: items.some((i) => i.type === "reasoning") ? "reasoning_only" : "empty_output" };
301
+ }
302
+ return { status: "completed", items };
303
+ }
304
+ case "response.failed": {
305
+ const response = ev.response ?? {};
306
+ const p = parseErrorObject({ error: response.error ?? {} }, Date.now());
307
+ throw toError({ ...p, code: p.code ?? "unknown_error", status: statusForCode(p.code) });
308
+ }
309
+ case "error": {
310
+ const p = parseErrorObject(ev, Date.now());
311
+ throw toError({ ...p, code: p.code ?? "unknown_error", status: statusForCode(p.code) });
312
+ }
313
+ default:
314
+ return null;
315
+ }
316
+ }
317
+ items(fallbackOutput) {
318
+ const entries = [...this.done];
319
+ for (const p of this.pending) {
320
+ if (!p.done)
321
+ entries.push({ item: { ...p.item, arguments: p.args }, outputIndex: p.outputIndex, seq: p.seq });
322
+ }
323
+ if (entries.length === 0 && Array.isArray(fallbackOutput)) {
324
+ return fallbackOutput.filter((i) => !!i && typeof i === "object" && typeof i.type === "string");
325
+ }
326
+ const byIndex = entries.every((e) => e.outputIndex !== void 0);
327
+ entries.sort((a, b) => byIndex ? a.outputIndex - b.outputIndex || a.seq - b.seq : a.seq - b.seq);
328
+ return entries.map((e) => e.item);
329
+ }
330
+ };
331
+ function statusForCode(code) {
332
+ switch (code) {
333
+ case "subscription_sharing_usage_limit_exceeded":
334
+ case "usage_limit_reached":
335
+ case "usage_not_included":
336
+ case "insufficient_quota":
337
+ return 429;
338
+ case "subscription_sharing_usage_unavailable":
339
+ case "subscription_sharing_user_unavailable":
340
+ return 503;
341
+ case "subscription_sharing_unsupported_capability":
342
+ return 400;
343
+ default:
344
+ return void 0;
345
+ }
346
+ }
347
+ function parseFrame(block) {
348
+ let event;
349
+ const data = [];
350
+ for (const line of block.split("\n")) {
351
+ if (line === "" || line.startsWith(":"))
352
+ continue;
353
+ const colon = line.indexOf(":");
354
+ const field = colon === -1 ? line : line.slice(0, colon);
355
+ const value = colon === -1 ? "" : line.slice(colon + 1).replace(/^ /, "");
356
+ if (field === "event")
357
+ event = value;
358
+ else if (field === "data")
359
+ data.push(value);
360
+ }
361
+ const raw = data.join("\n");
362
+ if (raw === "" || raw === "[DONE]")
363
+ return null;
364
+ let ev;
365
+ try {
366
+ ev = JSON.parse(raw);
367
+ } catch {
368
+ throw new ModelApiError({ message: `\u65E0\u6CD5\u89E3\u6790\u6A21\u578B\u4E8B\u4EF6\u6D41\uFF1A${raw.slice(0, 120)}` });
369
+ }
370
+ if (!ev || typeof ev !== "object")
371
+ return null;
372
+ const o = ev;
373
+ if (typeof o.type !== "string" && event)
374
+ o.type = event;
375
+ return o;
376
+ }
377
+ async function streamResponse(req, ctx) {
378
+ const body = JSON.stringify(requestBody(req));
379
+ const res = await send(ctx, `${ctx.profile.baseUrl}/responses`, (h) => ({
380
+ method: "POST",
381
+ headers: { ...h, "Content-Type": "application/json", Accept: "text/event-stream", session_id: req.sessionId },
382
+ body
383
+ }));
384
+ if (!res.body)
385
+ throw new ModelApiError({ status: res.status, message: "\u6A21\u578B\u63A5\u53E3\u8FD4\u56DE\u4E86\u7A7A\u7684\u54CD\u5E94\u6D41" });
386
+ const asm = new Assembler(ctx.onTextDelta);
387
+ const reader = res.body.getReader();
388
+ const decoder = new TextDecoder();
389
+ let buf = "";
390
+ try {
391
+ for (; ; ) {
392
+ let chunk;
393
+ try {
394
+ chunk = await reader.read();
395
+ } catch (e) {
396
+ if (ctx.signal.aborted)
397
+ throw ctx.signal.reason ?? e;
398
+ throw new ModelApiError({ message: `\u6A21\u578B\u4E8B\u4EF6\u6D41\u4E2D\u65AD\uFF1A${errMessage(e)}` }, { cause: e });
399
+ }
400
+ if (chunk.value)
401
+ buf = (buf + decoder.decode(chunk.value, { stream: true })).replace(/\r\n/g, "\n");
402
+ if (chunk.done)
403
+ buf = (buf + decoder.decode()).replace(/\r\n?/g, "\n") + "\n\n";
404
+ let at;
405
+ while ((at = buf.indexOf("\n\n")) !== -1) {
406
+ const block = buf.slice(0, at);
407
+ buf = buf.slice(at + 2);
408
+ const ev = parseFrame(block);
409
+ if (!ev)
410
+ continue;
411
+ const result = asm.handle(ev);
412
+ if (result)
413
+ return result;
414
+ }
415
+ if (chunk.done)
416
+ break;
417
+ }
418
+ } finally {
419
+ reader.cancel().catch(() => {
420
+ });
421
+ }
422
+ throw new ModelApiError({ code: "stream_ended", message: "\u6A21\u578B\u4E8B\u4EF6\u6D41\u5728 response.completed \u4E4B\u524D\u7ED3\u675F" });
423
+ }
424
+ export {
425
+ BYOK_PROFILE,
426
+ ByokRejectedError,
427
+ COMPAT_PROFILE,
428
+ ModelApiError,
429
+ ModelQuotaError,
430
+ QUOTA_CODES,
431
+ SIWC_PROFILE,
432
+ isFunctionCall,
433
+ isMessage,
434
+ listModels,
435
+ outputText,
436
+ streamResponse
437
+ };
@@ -0,0 +1,28 @@
1
+ import type { NemaConfig } from "./config.js";
2
+ import { type GraphifyOpts } from "./repos/graphify.js";
3
+ export type DoctorCheck = {
4
+ name: string;
5
+ ok: boolean;
6
+ detail: string;
7
+ };
8
+ export type DoctorOpts = {
9
+ codexBin: string;
10
+ userCodexHome: string;
11
+ hubUrl: string;
12
+ tokenFile: string;
13
+ /** Where Nema's own direct login lives (`<nemaHome>/auth.json`). */
14
+ nemaHome: string;
15
+ /** The configured adapter: a missing login only fails the check when this is codex-direct. */
16
+ adapter: NemaConfig["adapter"];
17
+ /** Defaults to siwc. compat reads the Codex CLI login instead of ~/.nema/auth.json. */
18
+ authMode?: NemaConfig["authMode"];
19
+ model?: NemaConfig["model"];
20
+ /** For tests: production passes nothing and runs `graphify` from PATH. */
21
+ graphify?: GraphifyOpts;
22
+ /** Home dir holding ~/.claude and ~/.codex for the report-hook rows. Defaults to os.homedir(). */
23
+ agentHome?: string;
24
+ /** For tests: production runs `github-mcp-server` from PATH. */
25
+ githubMcpServerBin?: string;
26
+ };
27
+ export declare function defaultTokenFile(env?: NodeJS.ProcessEnv): string;
28
+ export declare function runDoctor(opts: DoctorOpts): Promise<DoctorCheck[]>;