commandcode-api-proxy 0.1.2

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 (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +176 -0
  3. package/dist/auth.d.ts +7 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/auth.js +77 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/config.d.ts +22 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +67 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/logger.d.ts +9 -0
  12. package/dist/logger.d.ts.map +1 -0
  13. package/dist/logger.js +36 -0
  14. package/dist/logger.js.map +1 -0
  15. package/dist/models.json +89 -0
  16. package/dist/proxy.d.ts +3 -0
  17. package/dist/proxy.d.ts.map +1 -0
  18. package/dist/proxy.js +80 -0
  19. package/dist/proxy.js.map +1 -0
  20. package/dist/server.d.ts +4 -0
  21. package/dist/server.d.ts.map +1 -0
  22. package/dist/server.js +259 -0
  23. package/dist/server.js.map +1 -0
  24. package/dist/setup/opencode.d.ts +2 -0
  25. package/dist/setup/opencode.d.ts.map +1 -0
  26. package/dist/setup/opencode.js +58 -0
  27. package/dist/setup/opencode.js.map +1 -0
  28. package/dist/stream.d.ts +38 -0
  29. package/dist/stream.d.ts.map +1 -0
  30. package/dist/stream.js +89 -0
  31. package/dist/stream.js.map +1 -0
  32. package/dist/translate/models.d.ts +9 -0
  33. package/dist/translate/models.d.ts.map +1 -0
  34. package/dist/translate/models.js +41 -0
  35. package/dist/translate/models.js.map +1 -0
  36. package/dist/translate/openai.d.ts +13 -0
  37. package/dist/translate/openai.d.ts.map +1 -0
  38. package/dist/translate/openai.js +470 -0
  39. package/dist/translate/openai.js.map +1 -0
  40. package/dist/translate/types.d.ts +110 -0
  41. package/dist/translate/types.d.ts.map +1 -0
  42. package/dist/translate/types.js +3 -0
  43. package/dist/translate/types.js.map +1 -0
  44. package/dist/translate/util.d.ts +12 -0
  45. package/dist/translate/util.d.ts.map +1 -0
  46. package/dist/translate/util.js +114 -0
  47. package/dist/translate/util.js.map +1 -0
  48. package/dist/translate/validation.d.ts +6 -0
  49. package/dist/translate/validation.d.ts.map +1 -0
  50. package/dist/translate/validation.js +34 -0
  51. package/dist/translate/validation.js.map +1 -0
  52. package/dist/upstream.d.ts +41 -0
  53. package/dist/upstream.d.ts.map +1 -0
  54. package/dist/upstream.js +167 -0
  55. package/dist/upstream.js.map +1 -0
  56. package/package.json +55 -0
@@ -0,0 +1,114 @@
1
+ import crypto from "node:crypto";
2
+ // ──────────────────────────────────────────
3
+ // Message ID
4
+ // ──────────────────────────────────────────
5
+ // One id per downstream request, shared by the OpenAI encoders.
6
+ let _messageId = crypto.randomUUID();
7
+ export function getMessageId() {
8
+ return _messageId;
9
+ }
10
+ export function resetMessageId() {
11
+ _messageId = crypto.randomUUID();
12
+ }
13
+ // ──────────────────────────────────────────
14
+ // Usage extraction
15
+ // ──────────────────────────────────────────
16
+ export function extractUsage(data) {
17
+ // The CC upstream reports usage under `totalUsage` with AI SDK camelCase fields
18
+ // (inputTokens/outputTokens/...). Some upstreams may use `usage` with snake_case.
19
+ // Support both shapes.
20
+ const u = (data.totalUsage ?? data.usage);
21
+ if (!u)
22
+ return undefined;
23
+ const num = (v) => {
24
+ if (typeof v === "number")
25
+ return v;
26
+ if (typeof v === "string" && v.trim() !== "") {
27
+ const n = Number(v);
28
+ return Number.isFinite(n) ? n : undefined;
29
+ }
30
+ return undefined;
31
+ };
32
+ const promptTokens = num(u.promptTokens ?? u.inputTokens ?? u.prompt_tokens);
33
+ const completionTokens = num(u.completionTokens ?? u.outputTokens ?? u.completion_tokens);
34
+ const totalTokens = num(u.totalTokens ?? u.total_tokens) ??
35
+ (promptTokens != null && completionTokens != null
36
+ ? promptTokens + completionTokens
37
+ : undefined);
38
+ const cached = num(u.cachedInputTokens) ??
39
+ num(u.inputTokenDetails?.cacheReadTokens) ??
40
+ num(u.promptTokensDetails?.cachedTokens) ??
41
+ num(u.prompt_tokens_details?.cached_tokens);
42
+ const reasoning = num(u.reasoningTokens) ??
43
+ num(u.outputTokenDetails?.reasoningTokens) ??
44
+ num(u.completion_tokens_details?.reasoning_tokens);
45
+ const result = {};
46
+ if (promptTokens != null)
47
+ result.promptTokens = promptTokens;
48
+ if (completionTokens != null)
49
+ result.completionTokens = completionTokens;
50
+ if (totalTokens != null)
51
+ result.totalTokens = totalTokens;
52
+ if (cached != null)
53
+ result.promptTokensDetails = { cachedTokens: cached };
54
+ if (reasoning != null)
55
+ result.completionTokensDetails = { reasoningTokens: reasoning };
56
+ return Object.keys(result).length > 0 ? result : undefined;
57
+ }
58
+ // ──────────────────────────────────────────
59
+ // Shared CC request config
60
+ // ──────────────────────────────────────────
61
+ export function buildCCConfig(overrides) {
62
+ return {
63
+ workingDir: process.cwd(),
64
+ date: new Date().toISOString().slice(0, 10),
65
+ environment: `linux-x64, Node.js ${process.version}`,
66
+ structure: [],
67
+ isGitRepo: false,
68
+ currentBranch: "",
69
+ mainBranch: "",
70
+ gitStatus: "",
71
+ recentCommits: [],
72
+ ...overrides,
73
+ };
74
+ }
75
+ // ──────────────────────────────────────────
76
+ // Tool-call / tool-result pairing
77
+ // ──────────────────────────────────────────
78
+ /**
79
+ * CC rejects requests that contain a tool-call with no matching tool-result,
80
+ * or a tool-result with no matching tool-call. Drop any unpaired parts so the
81
+ * conversation always satisfies CC's pairing requirement.
82
+ */
83
+ export function pruneDanglingTools(messages) {
84
+ const callIds = new Set();
85
+ const resultIds = new Set();
86
+ for (const msg of messages) {
87
+ if (!Array.isArray(msg.content))
88
+ continue;
89
+ for (const part of msg.content) {
90
+ if (part.type === "tool-call" && part.toolCallId)
91
+ callIds.add(part.toolCallId);
92
+ if (part.type === "tool-result" && part.toolCallId)
93
+ resultIds.add(part.toolCallId);
94
+ }
95
+ }
96
+ const validIds = new Set([...callIds].filter((id) => resultIds.has(id)));
97
+ if (validIds.size === callIds.size && validIds.size === resultIds.size) {
98
+ return messages;
99
+ }
100
+ const result = [];
101
+ for (const msg of messages) {
102
+ if (!Array.isArray(msg.content)) {
103
+ result.push(msg);
104
+ continue;
105
+ }
106
+ const filtered = msg.content.filter((part) => (part.type !== "tool-call" && part.type !== "tool-result") ||
107
+ (part.toolCallId != null && validIds.has(part.toolCallId)));
108
+ if (filtered.length > 0) {
109
+ result.push({ role: msg.role, content: filtered });
110
+ }
111
+ }
112
+ return result;
113
+ }
114
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/translate/util.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { OpenAIChatRequest } from "./types.js";
2
+ export declare class ValidationError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ export declare function validateOpenAIChatRequest(body: unknown): OpenAIChatRequest;
6
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/translate/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,OAAO,GAAG,iBAAiB,CA+B1E"}
@@ -0,0 +1,34 @@
1
+ export class ValidationError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = "ValidationError";
5
+ }
6
+ }
7
+ export function validateOpenAIChatRequest(body) {
8
+ if (!body || typeof body !== "object") {
9
+ throw new ValidationError("Request body must be a JSON object");
10
+ }
11
+ const req = body;
12
+ if (req.model !== undefined && typeof req.model !== "string") {
13
+ throw new ValidationError("Field 'model' must be a string");
14
+ }
15
+ if (!Array.isArray(req.messages)) {
16
+ throw new ValidationError("Field 'messages' must be an array");
17
+ }
18
+ for (let i = 0; i < req.messages.length; i++) {
19
+ const msg = req.messages[i];
20
+ if (!msg || typeof msg !== "object") {
21
+ throw new ValidationError(`messages[${i}] must be an object`);
22
+ }
23
+ const m = msg;
24
+ const validRoles = ["system", "developer", "user", "assistant", "tool"];
25
+ if (typeof m.role !== "string" || !validRoles.includes(m.role)) {
26
+ throw new ValidationError(`messages[${i}].role must be one of: ${validRoles.join(", ")}`);
27
+ }
28
+ if (m.content === undefined && m.tool_calls === undefined) {
29
+ throw new ValidationError(`messages[${i}] must contain either 'content' or 'tool_calls'`);
30
+ }
31
+ }
32
+ return body;
33
+ }
34
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/translate/validation.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACxC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,MAAM,UAAU,yBAAyB,CAAC,IAAa;IACrD,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtC,MAAM,IAAI,eAAe,CAAC,oCAAoC,CAAC,CAAC;IAClE,CAAC;IAED,MAAM,GAAG,GAAG,IAA+B,CAAC;IAE5C,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC7D,MAAM,IAAI,eAAe,CAAC,gCAAgC,CAAC,CAAC;IAC9D,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,eAAe,CAAC,mCAAmC,CAAC,CAAC;IACjE,CAAC;IAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7C,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;YACpC,MAAM,IAAI,eAAe,CAAC,YAAY,CAAC,qBAAqB,CAAC,CAAC;QAChE,CAAC;QACD,MAAM,CAAC,GAAG,GAA8B,CAAC;QACzC,MAAM,UAAU,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;QACxE,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/D,MAAM,IAAI,eAAe,CAAC,YAAY,CAAC,0BAA0B,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5F,CAAC;QACD,IAAI,CAAC,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YAC1D,MAAM,IAAI,eAAe,CAAC,YAAY,CAAC,iDAAiD,CAAC,CAAC;QAC5F,CAAC;IACH,CAAC;IAED,OAAO,IAAyB,CAAC;AACnC,CAAC"}
@@ -0,0 +1,41 @@
1
+ import type { CCEvent, CCRequestBody } from "./translate/types.js";
2
+ interface UpstreamOptions {
3
+ apiBase: string;
4
+ apiKey: string;
5
+ ccVersion: string;
6
+ timeoutMs?: number;
7
+ }
8
+ /**
9
+ * Build the header set the official Command Code CLI sends. CC's server
10
+ * inspects these and rejects requests that look like a proxy ("Proxy use
11
+ * detected") if any of the CLI-identifying headers are missing/stale.
12
+ */
13
+ export declare function buildHeaders(apiKey: string, ccVersion: string, body: CCRequestBody): Record<string, string>;
14
+ /**
15
+ * Send a request to the Command Code /alpha/generate endpoint and parse
16
+ * the NDJSON response into CCEvent objects.
17
+ *
18
+ * CC's upstream is always streaming; we force `params.stream = true` here
19
+ * regardless of the downstream client's `stream` flag. For non-streaming
20
+ * downstream requests, the caller drains the returned `stream` into events.
21
+ *
22
+ * Returns a Readable of parsed CCEvents. Callers MUST consume or destroy it.
23
+ */
24
+ export declare function sendToCC(body: CCRequestBody, options: UpstreamOptions, signal?: AbortSignal): Promise<{
25
+ stream: NodeJS.ReadableStream;
26
+ }>;
27
+ /**
28
+ * Drain a CCEvent stream into an array. Used for non-streaming downstream
29
+ * requests where we need the full response before replying.
30
+ */
31
+ export declare function collectEvents(stream: NodeJS.ReadableStream): Promise<CCEvent[]>;
32
+ /**
33
+ * Error class for upstream CC API errors.
34
+ */
35
+ export declare class UpstreamError extends Error {
36
+ statusCode: number;
37
+ isRetryable: boolean;
38
+ constructor(message: string, statusCode: number, isRetryable: boolean);
39
+ }
40
+ export {};
41
+ //# sourceMappingURL=upstream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upstream.d.ts","sourceRoot":"","sources":["../src/upstream.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGnE,UAAU,eAAe;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,aAAa,GAClB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBxB;AAmBD;;;;;;;;;GASG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,eAAe,EACxB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC,cAAc,CAAA;CAAE,CAAC,CA8C5C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAO/E;AAED;;GAEG;AACH,qBAAa,aAAc,SAAQ,KAAK;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO;CAMtE"}
@@ -0,0 +1,167 @@
1
+ import crypto from "node:crypto";
2
+ import { Readable } from "node:stream";
3
+ import { parseCCLine } from "./stream.js";
4
+ import { logger } from "./logger.js";
5
+ /**
6
+ * Build the header set the official Command Code CLI sends. CC's server
7
+ * inspects these and rejects requests that look like a proxy ("Proxy use
8
+ * detected") if any of the CLI-identifying headers are missing/stale.
9
+ */
10
+ export function buildHeaders(apiKey, ccVersion, body) {
11
+ const sessionId = body.threadId;
12
+ logger.debug(`Sending Authorization: Bearer ${apiKey.slice(0, 12)}... (length: ${apiKey.length})`);
13
+ return {
14
+ "Content-Type": "application/json",
15
+ Accept: "application/json, */*",
16
+ "Accept-Encoding": "gzip, deflate, br",
17
+ "Accept-Language": "en-US,en;q=0.9",
18
+ Connection: "keep-alive",
19
+ "User-Agent": `commandcode-cli/${ccVersion} Node.js/${process.version}`,
20
+ Authorization: `Bearer ${apiKey}`,
21
+ "x-cli-environment": "production",
22
+ "x-command-code-version": ccVersion,
23
+ "x-session-id": sessionId,
24
+ "x-co-flag": "false",
25
+ "x-taste-learning": "false",
26
+ "x-project-slug": slugifyWorkingDir(body.config.workingDir),
27
+ traceparent: generateTraceparent(),
28
+ };
29
+ }
30
+ function slugifyWorkingDir(workingDir) {
31
+ const base = (workingDir || process.cwd()).split(/[/\\]/).filter(Boolean).pop() ?? "commandcode-proxy";
32
+ return (base
33
+ .toLowerCase()
34
+ .replace(/[^a-z0-9-]/g, "-")
35
+ .slice(0, 40) || "commandcode-proxy");
36
+ }
37
+ function generateTraceparent() {
38
+ const traceId = crypto.randomBytes(16).toString("hex");
39
+ const parentId = crypto.randomBytes(8).toString("hex");
40
+ return `00-${traceId}-${parentId}-01`;
41
+ }
42
+ /**
43
+ * Send a request to the Command Code /alpha/generate endpoint and parse
44
+ * the NDJSON response into CCEvent objects.
45
+ *
46
+ * CC's upstream is always streaming; we force `params.stream = true` here
47
+ * regardless of the downstream client's `stream` flag. For non-streaming
48
+ * downstream requests, the caller drains the returned `stream` into events.
49
+ *
50
+ * Returns a Readable of parsed CCEvents. Callers MUST consume or destroy it.
51
+ */
52
+ export async function sendToCC(body, options, signal) {
53
+ const { apiBase, apiKey, ccVersion, timeoutMs = 300_000 } = options;
54
+ const url = `${apiBase}/alpha/generate`;
55
+ // CC's API is always streaming — force it on so the upstream stays a stream.
56
+ body.params.stream = true;
57
+ const controller = new AbortController();
58
+ const combinedSignal = signal ? combineSignals(signal, controller.signal) : controller.signal;
59
+ const timeout = setTimeout(() => controller.abort(new Error("Upstream timeout")), timeoutMs);
60
+ try {
61
+ const response = await fetch(url, {
62
+ method: "POST",
63
+ headers: buildHeaders(apiKey, ccVersion, body),
64
+ body: JSON.stringify(body),
65
+ signal: combinedSignal,
66
+ });
67
+ clearTimeout(timeout);
68
+ if (!response.ok) {
69
+ const errorText = await response.text().catch(() => "");
70
+ const isRetryable = response.status >= 500 || response.status === 429;
71
+ throw new UpstreamError(`CC API ${response.status}: ${errorText || response.statusText}`, response.status, isRetryable);
72
+ }
73
+ if (!response.body) {
74
+ throw new UpstreamError("CC API returned no body", 0, true);
75
+ }
76
+ const stream = nodeReaderToStream(response.body.getReader());
77
+ return { stream };
78
+ }
79
+ catch (err) {
80
+ clearTimeout(timeout);
81
+ if (err instanceof UpstreamError)
82
+ throw err;
83
+ if (err.name === "AbortError") {
84
+ throw new UpstreamError("Request aborted", 0, true);
85
+ }
86
+ throw new UpstreamError(`Upstream request failed: ${err.message}`, 0, true);
87
+ }
88
+ }
89
+ /**
90
+ * Drain a CCEvent stream into an array. Used for non-streaming downstream
91
+ * requests where we need the full response before replying.
92
+ */
93
+ export function collectEvents(stream) {
94
+ const events = [];
95
+ return new Promise((resolve, reject) => {
96
+ stream.on("data", (event) => events.push(event));
97
+ stream.on("end", () => resolve(events));
98
+ stream.on("error", reject);
99
+ });
100
+ }
101
+ /**
102
+ * Error class for upstream CC API errors.
103
+ */
104
+ export class UpstreamError extends Error {
105
+ statusCode;
106
+ isRetryable;
107
+ constructor(message, statusCode, isRetryable) {
108
+ super(message);
109
+ this.name = "UpstreamError";
110
+ this.statusCode = statusCode;
111
+ this.isRetryable = isRetryable;
112
+ }
113
+ }
114
+ // ──────────────────────────────────────────
115
+ // Helpers
116
+ // ──────────────────────────────────────────
117
+ function combineSignals(...signals) {
118
+ const controller = new AbortController();
119
+ for (const signal of signals) {
120
+ if (signal.aborted) {
121
+ controller.abort(signal.reason);
122
+ return controller.signal;
123
+ }
124
+ signal.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
125
+ }
126
+ return controller.signal;
127
+ }
128
+ function nodeReaderToStream(reader) {
129
+ const decoder = new TextDecoder();
130
+ let buffer = "";
131
+ return new Readable({
132
+ objectMode: true,
133
+ async read() {
134
+ try {
135
+ while (true) {
136
+ const { done, value } = await reader.read();
137
+ if (done) {
138
+ // Flush remaining buffer
139
+ if (buffer.trim()) {
140
+ const result = parseCCLine(buffer);
141
+ if (result.type === "event" && result.event) {
142
+ this.push(result.event);
143
+ }
144
+ }
145
+ this.push(null);
146
+ return;
147
+ }
148
+ buffer += decoder.decode(value, { stream: true });
149
+ const lines = buffer.split("\n");
150
+ buffer = lines.pop() ?? "";
151
+ for (const line of lines) {
152
+ const result = parseCCLine(line);
153
+ if (result.type === "event" && result.event) {
154
+ const canContinue = this.push(result.event);
155
+ if (!canContinue)
156
+ return;
157
+ }
158
+ }
159
+ }
160
+ }
161
+ catch (err) {
162
+ this.destroy(err);
163
+ }
164
+ },
165
+ });
166
+ }
167
+ //# sourceMappingURL=upstream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upstream.js","sourceRoot":"","sources":["../src/upstream.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "commandcode-api-proxy",
3
+ "version": "0.1.2",
4
+ "description": "OpenAI-compatible API proxy for Command Code. Use your Command Code subscription from any OpenAI client.",
5
+ "keywords": [
6
+ "ai-proxy",
7
+ "claude-code",
8
+ "command-code",
9
+ "commandcode",
10
+ "llm",
11
+ "openai-proxy",
12
+ "opencode"
13
+ ],
14
+ "homepage": "https://github.com/thaolaptrinh/commandcode-api-proxy",
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/thaolaptrinh/commandcode-api-proxy.git"
19
+ },
20
+ "bin": {
21
+ "commandcode-api-proxy": "./dist/proxy.js"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "README.md",
26
+ "LICENSE"
27
+ ],
28
+ "type": "module",
29
+ "main": "./dist/proxy.js",
30
+ "devDependencies": {
31
+ "@types/node": "^24.0.0",
32
+ "ai": "^5.0.206",
33
+ "tsc-alias": "^1.8.17",
34
+ "tsx": "^4.19.0",
35
+ "typescript": "^5.7.0",
36
+ "vite-plus": "^0.2.1",
37
+ "vitest": "^3.0.0"
38
+ },
39
+ "engines": {
40
+ "node": ">=24.0.0"
41
+ },
42
+ "scripts": {
43
+ "build": "tsc && tsc-alias",
44
+ "start": "node dist/proxy.js",
45
+ "dev": "tsx src/proxy.ts",
46
+ "test": "vitest run",
47
+ "test:watch": "vitest",
48
+ "test:coverage": "vitest run --coverage",
49
+ "lint": "vp lint",
50
+ "fmt": "vp fmt",
51
+ "fmt:check": "vp fmt --check",
52
+ "auth": "tsx src/proxy.ts auth",
53
+ "check": "vp check"
54
+ }
55
+ }