codex-copilot-dx 0.6.21 → 0.7.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.
Files changed (42) hide show
  1. package/README.md +142 -323
  2. package/bin/cli.mjs +26 -130
  3. package/package.json +2 -8
  4. package/scripts/benchmark.mjs +0 -114
  5. package/src/adapter.mjs +22 -107
  6. package/src/auth-profile.mjs +18 -130
  7. package/src/cli-animation.mjs +108 -0
  8. package/src/cli-auth.mjs +44 -398
  9. package/src/cli-models.mjs +21 -49
  10. package/src/cli-options.mjs +53 -75
  11. package/src/cli-status.mjs +6 -23
  12. package/src/config.mjs +2 -8
  13. package/src/copilot.mjs +2 -2
  14. package/src/doctor.mjs +44 -291
  15. package/src/model-cache.mjs +4 -11
  16. package/src/models.mjs +0 -184
  17. package/src/observability.mjs +1 -38
  18. package/src/profile-model-runtime.mjs +61 -189
  19. package/src/profile-routing.mjs +2 -102
  20. package/src/profile-runtime.mjs +15 -68
  21. package/src/responses-handler.mjs +1 -1
  22. package/src/responses-proxy.mjs +1 -1
  23. package/src/running-adapter.mjs +2 -4
  24. package/src/stream-errors.mjs +3 -6
  25. package/src/stream-performance.mjs +1 -1
  26. package/src/terminal-activity.mjs +40 -34
  27. package/src/terminal-animation.mjs +249 -0
  28. package/src/usage.mjs +0 -31
  29. package/src/user-settings.mjs +50 -0
  30. package/src/anthropic-handler.mjs +0 -195
  31. package/src/anthropic.mjs +0 -387
  32. package/src/claude-config.mjs +0 -57
  33. package/src/claude-desktop-config.mjs +0 -317
  34. package/src/cli-error.mjs +0 -50
  35. package/src/pm-studio-asar.mjs +0 -1244
  36. package/src/pm-studio-bundle-content.mjs +0 -165
  37. package/src/pm-studio-relay.mjs +0 -563
  38. package/src/pm-studio-setup-lock.mjs +0 -336
  39. package/src/pm-studio-setup.mjs +0 -1581
  40. package/src/pm-studio-status.mjs +0 -369
  41. package/src/token-counter-service.mjs +0 -211
  42. package/src/token-counter-worker.mjs +0 -16
package/src/usage.mjs CHANGED
@@ -56,20 +56,6 @@ function normalizeResponsesUsage(usage) {
56
56
  return hasPositiveTokenValue(out) ? out : undefined;
57
57
  }
58
58
 
59
- function normalizeAnthropicUsage(usage) {
60
- if (!usage || typeof usage !== "object") return undefined;
61
- const out = compactObject({
62
- input_tokens: positiveNumber(usage.input_tokens),
63
- cache_read_input_tokens: positiveNumber(usage.cache_read_input_tokens),
64
- cache_creation_input_tokens: positiveNumber(usage.cache_creation_input_tokens),
65
- output_tokens: positiveNumber(usage.output_tokens),
66
- });
67
- if (out.input_tokens !== undefined || out.cache_read_input_tokens !== undefined || out.cache_creation_input_tokens !== undefined || out.output_tokens !== undefined) {
68
- out.total_tokens = (out.input_tokens || 0) + (out.cache_read_input_tokens || 0) + (out.cache_creation_input_tokens || 0) + (out.output_tokens || 0);
69
- }
70
- return hasPositiveTokenValue(out) ? out : undefined;
71
- }
72
-
73
59
  function normalizeCopilotUsage(copilotUsage) {
74
60
  if (!copilotUsage || typeof copilotUsage !== "object") return undefined;
75
61
  const out = {};
@@ -112,19 +98,6 @@ export function buildResponsesUsageRecord({ surface = "responses", mode, model,
112
98
  });
113
99
  }
114
100
 
115
- export function buildAnthropicUsageRecord({ surface = "messages", mode, model, responseId, usage } = {}) {
116
- const normalized = normalizeAnthropicUsage(usage);
117
- if (!normalized) return null;
118
- return compactObject({
119
- ts: new Date().toISOString(),
120
- surface,
121
- mode,
122
- model,
123
- response_id: responseId,
124
- usage: normalized,
125
- });
126
- }
127
-
128
101
  function takePendingWrites(filePath, maxBytes) {
129
102
  const batch = [];
130
103
  let retained = 0;
@@ -188,10 +161,6 @@ export function recordResponsesUsage(args) {
188
161
  return recordUsage(buildResponsesUsageRecord(args));
189
162
  }
190
163
 
191
- export function recordAnthropicUsage(args) {
192
- return recordUsage(buildAnthropicUsageRecord(args));
193
- }
194
-
195
164
  export async function flushUsageWritesForTests() {
196
165
  while (writeDrain || pendingWrites.length > 0) {
197
166
  if (writeDrain) await writeDrain;
@@ -3,8 +3,14 @@ import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { atomicWriteFileIfChangedSync } from "./atomic-file.mjs";
5
5
  import { DEFAULT_CODEX_AUTO_REVIEW_MODEL } from "./models.mjs";
6
+ import {
7
+ DEFAULT_TERMINAL_ANIMATION_THEME,
8
+ isTerminalAnimationTheme,
9
+ TERMINAL_ANIMATION_THEMES,
10
+ } from "./terminal-animation.mjs";
6
11
 
7
12
  const AUTO_REVIEW_MODEL_KEY = "auto_review_model";
13
+ const TERMINAL_ANIMATION_KEY = "terminal_animation";
8
14
 
9
15
  export function userSettingsPath({ env = process.env, home = os.homedir() } = {}) {
10
16
  const xdgConfigHome = String(env.XDG_CONFIG_HOME || "").trim();
@@ -29,6 +35,16 @@ export function readUserSettings({ env = process.env, home = os.homedir(), stric
29
35
  throw invalidSettings(filePath, `${AUTO_REVIEW_MODEL_KEY} must be a non-empty string`);
30
36
  }
31
37
  }
38
+ if (Object.hasOwn(parsed, TERMINAL_ANIMATION_KEY)) {
39
+ const value = parsed[TERMINAL_ANIMATION_KEY];
40
+ if (!isTerminalAnimationTheme(value)) {
41
+ const choices = TERMINAL_ANIMATION_THEMES.map(({ id }) => id).join(", ");
42
+ if (strict) {
43
+ throw invalidSettings(filePath, `${TERMINAL_ANIMATION_KEY} must be one of: ${choices}`);
44
+ }
45
+ delete parsed[TERMINAL_ANIMATION_KEY];
46
+ }
47
+ }
32
48
  return parsed;
33
49
  } catch (error) {
34
50
  if (error?.code === "ENOENT") return {};
@@ -54,6 +70,18 @@ export function autoReviewModelPreference({ env = process.env, home = os.homedir
54
70
  return { model: DEFAULT_CODEX_AUTO_REVIEW_MODEL, source: "default" };
55
71
  }
56
72
 
73
+ export function savedTerminalAnimationTheme(options = {}) {
74
+ const settings = readUserSettings(options);
75
+ const theme = settings[TERMINAL_ANIMATION_KEY];
76
+ return isTerminalAnimationTheme(theme) ? theme : "";
77
+ }
78
+
79
+ export function terminalAnimationPreference({ env = process.env, home = os.homedir() } = {}) {
80
+ const savedTheme = savedTerminalAnimationTheme({ env, home });
81
+ if (savedTheme) return { theme: savedTheme, source: "settings" };
82
+ return { theme: DEFAULT_TERMINAL_ANIMATION_THEME, source: "default" };
83
+ }
84
+
57
85
  export function writeAutoReviewModel(model, { env = process.env, home = os.homedir() } = {}) {
58
86
  const filePath = userSettingsPath({ env, home });
59
87
  const settings = readUserSettings({ env, home, strict: true });
@@ -65,3 +93,25 @@ export function writeAutoReviewModel(model, { env = process.env, home = os.homed
65
93
  const changed = atomicWriteFileIfChangedSync(filePath, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
66
94
  return { changed, filePath, model: value || DEFAULT_CODEX_AUTO_REVIEW_MODEL };
67
95
  }
96
+
97
+ export function writeTerminalAnimationTheme(theme, { env = process.env, home = os.homedir() } = {}) {
98
+ if (!isTerminalAnimationTheme(theme)) {
99
+ const choices = TERMINAL_ANIMATION_THEMES.map(({ id }) => id).join(", ");
100
+ throw new Error(`Terminal animation must be one of: ${choices}`);
101
+ }
102
+
103
+ const filePath = userSettingsPath({ env, home });
104
+ const settings = readUserSettings({ env, home, strict: true });
105
+ const next = { ...settings };
106
+ if (theme === DEFAULT_TERMINAL_ANIMATION_THEME) {
107
+ if (!Object.hasOwn(next, TERMINAL_ANIMATION_KEY)) {
108
+ return { changed: false, filePath, theme };
109
+ }
110
+ delete next[TERMINAL_ANIMATION_KEY];
111
+ } else {
112
+ next[TERMINAL_ANIMATION_KEY] = theme;
113
+ }
114
+
115
+ const changed = atomicWriteFileIfChangedSync(filePath, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
116
+ return { changed, filePath, theme };
117
+ }
@@ -1,195 +0,0 @@
1
- import {
2
- anthropicToChat,
3
- chatToAnthropic,
4
- countTokens,
5
- streamAnthropicFromLines,
6
- } from "./anthropic.mjs";
7
- import {
8
- createRequestAbort,
9
- logRequestFailure,
10
- MAX_UPSTREAM_CHAT_SUCCESS_BODY_BYTES,
11
- MAX_UPSTREAM_ERROR_BODY_BYTES,
12
- readJsonBody,
13
- readBoundedResponseText,
14
- sendJsonError,
15
- sendUpstreamError,
16
- writeOrDrain,
17
- } from "./http-transport.mjs";
18
- import { resolveAnthropicModel } from "./models.mjs";
19
- import { status } from "./status.mjs";
20
- import { webStreamLines } from "./stream.mjs";
21
- import { endStreamWithError } from "./stream-errors.mjs";
22
- import { requireUpstreamEventStream, withChatStreamUsage } from "./stream-contract.mjs";
23
- import { safeUpstreamResponseHeaders } from "./upstream-headers.mjs";
24
- import { recordAnthropicUsage } from "./usage.mjs";
25
-
26
- const PROMPT_TOO_LONG_MESSAGE = "prompt is too long: your prompt is too long. Please reduce the number of messages or use a model with a larger context window.";
27
- const DOWNSTREAM_CLOSED = Symbol("downstream-closed");
28
-
29
- function contextWindowErrorBody(text) {
30
- let error;
31
- try {
32
- const parsed = JSON.parse(text);
33
- error = parsed && typeof parsed === "object" ? parsed.error : null;
34
- } catch {
35
- error = { message: text };
36
- }
37
- if (!error || typeof error !== "object") return null;
38
- const code = error.code;
39
- const message = typeof error.message === "string" ? error.message.toLowerCase() : "";
40
- const matches = code === "context_length_exceeded"
41
- || code === "model_max_prompt_tokens_exceeded"
42
- || message.includes("exceeds the context window of this model")
43
- || message.includes("maximum context length is")
44
- || message.includes("request body is too large for model context window");
45
- if (!matches) return null;
46
- return JSON.stringify({
47
- type: "error",
48
- error: { type: "invalid_request_error", message: PROMPT_TOO_LONG_MESSAGE },
49
- });
50
- }
51
-
52
- function sendAnthropicUpstreamError(res, response, text) {
53
- const rewritten = contextWindowErrorBody(text);
54
- if (!rewritten) {
55
- sendUpstreamError(res, response, text);
56
- return;
57
- }
58
- const headers = new Headers(response.headers);
59
- headers.set("content-type", "application/json");
60
- sendUpstreamError(res, { status: response.status, headers }, rewritten);
61
- }
62
-
63
- export function createAnthropicCountTokensHandler(options) {
64
- const { acquireRequest, requestBodyTimeoutMs, countTokensFn = countTokens } = options;
65
-
66
- return async function handleAnthropicCountTokens(req, res) {
67
- const abort = createRequestAbort(req, res);
68
- let releaseRequest = () => {};
69
- try {
70
- releaseRequest = await acquireRequest(req, { signal: abort.signal });
71
- abort.setTimeout(requestBodyTimeoutMs, "request_body_timeout");
72
- const parsed = await readJsonBody(req, { admission: releaseRequest, signal: abort.signal });
73
- abort.clearTimeout();
74
- const result = await countTokensFn(parsed, { signal: abort.signal });
75
- releaseRequest();
76
- res.writeHead(200, { "Content-Type": "application/json" });
77
- res.end(JSON.stringify(result));
78
- } catch (error) {
79
- sendJsonError(res, error, abort.reason === "request_body_timeout" ? 408 : 500);
80
- } finally {
81
- releaseRequest();
82
- abort.cleanup();
83
- }
84
- };
85
- }
86
-
87
- export function createAnthropicMessagesHandler(options) {
88
- const {
89
- acquireRequest,
90
- chatCompletionsFn,
91
- environment,
92
- modelOptions,
93
- requestBodyTimeoutMs,
94
- streamHandshakeTimeoutMs,
95
- streamIdleTimeoutMs,
96
- upstreamTimeoutMs,
97
- } = options;
98
-
99
- return async function handleAnthropicMessages(req, res) {
100
- const abort = createRequestAbort(req, res);
101
- let releaseRequest = () => {};
102
- try {
103
- releaseRequest = await acquireRequest(req, { signal: abort.signal });
104
- abort.setTimeout(requestBodyTimeoutMs, "request_body_timeout");
105
- const parsed = await readJsonBody(req, { admission: releaseRequest, signal: abort.signal });
106
- abort.setTimeout(
107
- parsed.stream ? streamHandshakeTimeoutMs : upstreamTimeoutMs,
108
- parsed.stream ? "stream_handshake_timeout" : "upstream_timeout",
109
- );
110
- const { requestedModel, upstreamModel } = resolveAnthropicModel(parsed.model || "unknown", environment, modelOptions());
111
- const modelNote = upstreamModel === requestedModel ? requestedModel : `${requestedModel} -> ${upstreamModel}`;
112
- console.log(status("info", `messages model=${modelNote} stream=${!!parsed.stream}`));
113
- const chatReq = anthropicToChat(parsed, { upstreamModel });
114
- const forceRequestedModel = upstreamModel !== requestedModel;
115
- if (parsed.stream) {
116
- const upstream = await chatCompletionsFn(withChatStreamUsage(chatReq), { signal: abort.signal });
117
- releaseRequest();
118
- if (!upstream.ok) {
119
- sendAnthropicUpstreamError(res, upstream, await readBoundedResponseText(upstream, {
120
- maxBytes: MAX_UPSTREAM_ERROR_BODY_BYTES,
121
- label: "Copilot Chat error body",
122
- }));
123
- return;
124
- }
125
- await requireUpstreamEventStream(upstream);
126
- abort.setTimeout(streamIdleTimeoutMs, "stream_idle_timeout");
127
- res.writeHead(200, {
128
- ...safeUpstreamResponseHeaders(upstream.headers, { contentType: "text/event-stream" }),
129
- "Cache-Control": "no-cache",
130
- Connection: "keep-alive",
131
- });
132
- let messageId;
133
- let usage;
134
- try {
135
- await streamAnthropicFromLines(
136
- webStreamLines(upstream, {
137
- onChunk: () => abort.setTimeout(streamIdleTimeoutMs, "stream_idle_timeout"),
138
- }),
139
- async (event, data) => {
140
- if (event === "message_start") messageId = data.message?.id;
141
- if (event === "message_delta") usage = data.usage;
142
- if (!await writeOrDrain(res, `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`)) {
143
- throw DOWNSTREAM_CLOSED;
144
- }
145
- },
146
- requestedModel,
147
- { forceModel: forceRequestedModel },
148
- );
149
- } catch (error) {
150
- if (error === DOWNSTREAM_CLOSED) return;
151
- logRequestFailure("Messages", error, abort);
152
- await endStreamWithError(res, "anthropic", error, abort);
153
- return;
154
- }
155
- recordAnthropicUsage({ surface: "messages", mode: "stream", model: requestedModel, responseId: messageId, usage });
156
- if (!res.writableEnded) res.end();
157
- } else {
158
- const upstream = await chatCompletionsFn({ ...chatReq, stream: false }, { signal: abort.signal });
159
- releaseRequest();
160
- const data = upstream.ok
161
- ? await readBoundedResponseText(upstream, {
162
- maxBytes: MAX_UPSTREAM_CHAT_SUCCESS_BODY_BYTES,
163
- label: "Copilot Chat success body",
164
- signal: abort.signal,
165
- })
166
- : await readBoundedResponseText(upstream, {
167
- maxBytes: MAX_UPSTREAM_ERROR_BODY_BYTES,
168
- label: "Copilot Chat error body",
169
- });
170
- if (!upstream.ok) {
171
- sendAnthropicUpstreamError(res, upstream, data);
172
- return;
173
- }
174
- const anthropicMessage = chatToAnthropic(JSON.parse(data), requestedModel, { forceModel: forceRequestedModel });
175
- recordAnthropicUsage({
176
- surface: "messages",
177
- mode: "json",
178
- model: anthropicMessage.model,
179
- responseId: anthropicMessage.id,
180
- usage: anthropicMessage.usage,
181
- });
182
- res.writeHead(200, safeUpstreamResponseHeaders(upstream.headers, {
183
- contentType: "application/json",
184
- }));
185
- res.end(JSON.stringify(anthropicMessage));
186
- }
187
- } catch (error) {
188
- logRequestFailure("Messages", error, abort);
189
- sendJsonError(res, error, 502);
190
- } finally {
191
- releaseRequest();
192
- abort.cleanup();
193
- }
194
- };
195
- }