xcode-copilot-server 1.0.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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +147 -0
  3. package/config.json5 +49 -0
  4. package/dist/config-schema.d.ts +85 -0
  5. package/dist/config-schema.js +38 -0
  6. package/dist/config-schema.js.map +1 -0
  7. package/dist/config.d.ts +7 -0
  8. package/dist/config.js +64 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/context.d.ts +8 -0
  11. package/dist/context.js +2 -0
  12. package/dist/context.js.map +1 -0
  13. package/dist/copilot-service.d.ts +19 -0
  14. package/dist/copilot-service.js +49 -0
  15. package/dist/copilot-service.js.map +1 -0
  16. package/dist/handlers/completions/session-config.d.ts +12 -0
  17. package/dist/handlers/completions/session-config.js +63 -0
  18. package/dist/handlers/completions/session-config.js.map +1 -0
  19. package/dist/handlers/completions/streaming.d.ts +4 -0
  20. package/dist/handlers/completions/streaming.js +132 -0
  21. package/dist/handlers/completions/streaming.js.map +1 -0
  22. package/dist/handlers/completions.d.ts +4 -0
  23. package/dist/handlers/completions.js +106 -0
  24. package/dist/handlers/completions.js.map +1 -0
  25. package/dist/handlers/models.d.ts +4 -0
  26. package/dist/handlers/models.js +29 -0
  27. package/dist/handlers/models.js.map +1 -0
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +96 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/logger.d.ts +19 -0
  32. package/dist/logger.js +43 -0
  33. package/dist/logger.js.map +1 -0
  34. package/dist/schemas.d.ts +49 -0
  35. package/dist/schemas.js +75 -0
  36. package/dist/schemas.js.map +1 -0
  37. package/dist/server.d.ts +3 -0
  38. package/dist/server.js +41 -0
  39. package/dist/server.js.map +1 -0
  40. package/dist/types.d.ts +83 -0
  41. package/dist/types.js +2 -0
  42. package/dist/types.js.map +1 -0
  43. package/dist/utils/prompt.d.ts +10 -0
  44. package/dist/utils/prompt.js +48 -0
  45. package/dist/utils/prompt.js.map +1 -0
  46. package/package.json +48 -0
  47. package/scripts/mcpbridge-proxy.mjs +73 -0
@@ -0,0 +1,132 @@
1
+ import { truncate } from "../../logger.js";
2
+ import { currentTimestamp } from "../../schemas.js";
3
+ const REQUEST_TIMEOUT_MS = 5 * 60 * 1000;
4
+ export async function handleStreaming(reply, session, prompt, model, logger) {
5
+ reply.raw.writeHead(200, {
6
+ "Content-Type": "text/event-stream",
7
+ "Cache-Control": "no-cache",
8
+ Connection: "keep-alive",
9
+ "X-Accel-Buffering": "no",
10
+ });
11
+ const completionId = `chatcmpl-${String(Date.now())}`;
12
+ function sendChunk(delta, finishReason) {
13
+ const chunk = {
14
+ id: completionId,
15
+ object: "chat.completion.chunk",
16
+ created: currentTimestamp(),
17
+ model,
18
+ choices: [
19
+ {
20
+ index: 0,
21
+ delta,
22
+ finish_reason: finishReason,
23
+ },
24
+ ],
25
+ };
26
+ reply.raw.write(`data: ${JSON.stringify(chunk)}\n\n`);
27
+ }
28
+ sendChunk({ role: "assistant" }, null);
29
+ const { promise, resolve } = Promise.withResolvers();
30
+ let done = false;
31
+ function cleanup() {
32
+ done = true;
33
+ clearTimeout(timeout);
34
+ unsubscribe();
35
+ }
36
+ reply.raw.on("close", () => {
37
+ if (!done) {
38
+ logger.info("Client disconnected, aborting session");
39
+ cleanup();
40
+ session.abort().catch((err) => {
41
+ logger.error("Failed to abort session:", err);
42
+ });
43
+ resolve(undefined);
44
+ }
45
+ });
46
+ const timeout = setTimeout(() => {
47
+ logger.warn("Stream timed out after 5 minutes");
48
+ cleanup();
49
+ reply.raw.end();
50
+ resolve(undefined);
51
+ }, REQUEST_TIMEOUT_MS);
52
+ // Buffer deltas so we can discard intermediate narration
53
+ // (e.g. "Let me search...") that precedes tool calls.
54
+ let pendingDeltas = [];
55
+ // Track tool names by call ID so we can log them on completion
56
+ const toolNames = new Map();
57
+ function flushPending() {
58
+ for (const text of pendingDeltas) {
59
+ sendChunk({ content: text }, null);
60
+ }
61
+ pendingDeltas = [];
62
+ }
63
+ const unsubscribe = session.on((event) => {
64
+ if (event.type === "tool.execution_start") {
65
+ const d = event.data;
66
+ toolNames.set(d.toolCallId, d.toolName);
67
+ logger.debug(`Running ${d.toolName} (${truncate(d.arguments)})`);
68
+ return;
69
+ }
70
+ if (event.type === "tool.execution_complete") {
71
+ const d = event.data;
72
+ const name = toolNames.get(d.toolCallId) ?? d.toolCallId;
73
+ toolNames.delete(d.toolCallId);
74
+ const detail = d.success
75
+ ? truncate(d.result?.content)
76
+ : d.error?.message ?? "failed";
77
+ logger.debug(`${name} done (${detail})`);
78
+ return;
79
+ }
80
+ switch (event.type) {
81
+ case "assistant.message_delta":
82
+ if (event.data.deltaContent) {
83
+ pendingDeltas.push(event.data.deltaContent);
84
+ }
85
+ break;
86
+ case "assistant.message":
87
+ if (event.data.toolRequests && event.data.toolRequests.length > 0) {
88
+ // Intermediate turn, so discard buffered narration.
89
+ logger.debug(`Calling tools (dropping buffered text): ${event.data.toolRequests.map((tr) => tr.name).join(", ")}`);
90
+ pendingDeltas = [];
91
+ }
92
+ else {
93
+ // Final turn, so flush buffered deltas to the client.
94
+ flushPending();
95
+ }
96
+ break;
97
+ case "session.idle":
98
+ logger.info("Done, wrapping up stream");
99
+ flushPending();
100
+ cleanup();
101
+ sendChunk({}, "stop");
102
+ reply.raw.write("data: [DONE]\n\n");
103
+ reply.raw.end();
104
+ resolve(undefined);
105
+ break;
106
+ case "session.compaction_start":
107
+ logger.info("Compacting context...");
108
+ break;
109
+ case "session.compaction_complete": {
110
+ const cd = event.data;
111
+ logger.info(`Context compacted: ${String(cd.preCompactionTokens)} → ${String(cd.postCompactionTokens)} tokens`);
112
+ break;
113
+ }
114
+ case "session.error":
115
+ logger.error(`Session error: ${event.data.message}`);
116
+ cleanup();
117
+ reply.raw.end();
118
+ resolve(undefined);
119
+ break;
120
+ }
121
+ });
122
+ session.send({ prompt }).catch((err) => {
123
+ logger.error("Failed to send prompt:", err);
124
+ if (!done) {
125
+ cleanup();
126
+ reply.raw.end();
127
+ }
128
+ resolve(undefined);
129
+ });
130
+ return promise;
131
+ }
132
+ //# sourceMappingURL=streaming.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming.js","sourceRoot":"","sources":["../../../src/handlers/completions/streaming.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { FastifyRequest, FastifyReply } from "fastify";
2
+ import type { AppContext } from "../context.js";
3
+ /** POST /v1/chat/completions */
4
+ export declare function createCompletionsHandler({ service, logger, config }: AppContext): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
@@ -0,0 +1,106 @@
1
+ import { ChatCompletionRequestSchema, extractContentText } from "../schemas.js";
2
+ import { formatPrompt } from "../utils/prompt.js";
3
+ import { createSessionConfig } from "./completions/session-config.js";
4
+ import { handleStreaming } from "./completions/streaming.js";
5
+ /** POST /v1/chat/completions */
6
+ export function createCompletionsHandler({ service, logger, config }) {
7
+ return async function handleCompletions(request, reply) {
8
+ const parseResult = ChatCompletionRequestSchema.safeParse(request.body);
9
+ if (!parseResult.success) {
10
+ const firstIssue = parseResult.error.issues[0];
11
+ reply.status(400).send({
12
+ error: {
13
+ message: firstIssue?.message ?? "Invalid request body",
14
+ type: "invalid_request_error",
15
+ },
16
+ });
17
+ return;
18
+ }
19
+ const req = parseResult.data;
20
+ const messages = req.messages;
21
+ const systemParts = [];
22
+ for (const msg of messages) {
23
+ if (msg.role === "system" || msg.role === "developer") {
24
+ try {
25
+ systemParts.push(extractContentText(msg.content));
26
+ }
27
+ catch (err) {
28
+ reply.status(400).send({
29
+ error: {
30
+ message: err instanceof Error ? err.message : String(err),
31
+ type: "invalid_request_error",
32
+ },
33
+ });
34
+ return;
35
+ }
36
+ }
37
+ }
38
+ let prompt;
39
+ try {
40
+ prompt = formatPrompt(messages, config.excludedFilePatterns);
41
+ }
42
+ catch (err) {
43
+ reply.status(400).send({
44
+ error: {
45
+ message: err instanceof Error ? err.message : String(err),
46
+ type: "invalid_request_error",
47
+ },
48
+ });
49
+ return;
50
+ }
51
+ const systemMessage = systemParts.length > 0 ? systemParts.join("\n\n") : undefined;
52
+ let supportsReasoningEffort = false;
53
+ if (config.reasoningEffort) {
54
+ try {
55
+ const models = await service.listModels();
56
+ const modelInfo = models.find((m) => m.id === req.model);
57
+ supportsReasoningEffort =
58
+ modelInfo?.capabilities.supports.reasoningEffort ?? false;
59
+ if (!supportsReasoningEffort) {
60
+ logger.debug(`Model "${req.model}" does not support reasoning effort, ignoring config`);
61
+ }
62
+ }
63
+ catch (err) {
64
+ logger.warn("Failed to check model capabilities:", err);
65
+ }
66
+ }
67
+ const sessionConfig = createSessionConfig({
68
+ model: req.model,
69
+ systemMessage,
70
+ logger,
71
+ config,
72
+ supportsReasoningEffort,
73
+ cwd: service.cwd,
74
+ });
75
+ let session;
76
+ try {
77
+ session = await service.getSession(sessionConfig);
78
+ }
79
+ catch (err) {
80
+ logger.error("Getting session failed:", err);
81
+ reply.status(500).send({
82
+ error: {
83
+ message: "Failed to create session",
84
+ type: "api_error",
85
+ },
86
+ });
87
+ return;
88
+ }
89
+ try {
90
+ logger.info("Streaming response");
91
+ await handleStreaming(reply, session, prompt, req.model, logger);
92
+ }
93
+ catch (err) {
94
+ logger.error("Request failed:", err);
95
+ if (!reply.sent) {
96
+ reply.status(500).send({
97
+ error: {
98
+ message: err instanceof Error ? err.message : "Internal error",
99
+ type: "api_error",
100
+ },
101
+ });
102
+ }
103
+ }
104
+ };
105
+ }
106
+ //# sourceMappingURL=completions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completions.js","sourceRoot":"","sources":["../../src/handlers/completions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { FastifyRequest, FastifyReply } from "fastify";
2
+ import type { AppContext } from "../context.js";
3
+ /** GET /v1/models */
4
+ export declare function createModelsHandler({ service, logger }: AppContext): (_request: FastifyRequest, reply: FastifyReply) => Promise<void>;
@@ -0,0 +1,29 @@
1
+ import { currentTimestamp } from "../schemas.js";
2
+ /** GET /v1/models */
3
+ export function createModelsHandler({ service, logger }) {
4
+ return async function handleModels(_request, reply) {
5
+ try {
6
+ const models = await service.listModels();
7
+ const response = {
8
+ object: "list",
9
+ data: models.map((m) => ({
10
+ id: m.id,
11
+ object: "model",
12
+ created: currentTimestamp(),
13
+ owned_by: "github-copilot",
14
+ })),
15
+ };
16
+ reply.send(response);
17
+ }
18
+ catch (err) {
19
+ logger.error("Couldn't fetch models:", err);
20
+ reply.status(500).send({
21
+ error: {
22
+ message: "Failed to list models",
23
+ type: "api_error",
24
+ },
25
+ });
26
+ }
27
+ };
28
+ }
29
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/handlers/models.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGjD,qBAAqB;AACrB,MAAM,UAAU,mBAAmB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAc;IACjE,OAAO,KAAK,UAAU,YAAY,CAChC,QAAwB,EACxB,KAAmB;QAEnB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;YAE1C,MAAM,QAAQ,GAAmB;gBAC/B,MAAM,EAAE,MAAM;gBACd,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;oBACvB,EAAE,EAAE,CAAC,CAAC,EAAE;oBACR,MAAM,EAAE,OAAO;oBACf,OAAO,EAAE,gBAAgB,EAAE;oBAC3B,QAAQ,EAAE,gBAAgB;iBAC3B,CAAC,CAAC;aACJ,CAAC;YAEF,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,wBAAwB,EAAE,GAAG,CAAC,CAAC;YAC5C,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACrB,KAAK,EAAE;oBACL,OAAO,EAAE,uBAAuB;oBAChC,IAAI,EAAE,WAAW;iBAClB;aACF,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ import { join, dirname } from "node:path";
3
+ import { parseArgs } from "node:util";
4
+ import { CopilotService } from "./copilot-service.js";
5
+ import { loadConfig } from "./config.js";
6
+ import { createServer } from "./server.js";
7
+ import { Logger, LEVEL_PRIORITY } from "./logger.js";
8
+ const PACKAGE_ROOT = dirname(import.meta.dirname);
9
+ const DEFAULT_CONFIG_PATH = join(PACKAGE_ROOT, "config.json5");
10
+ const VALID_LOG_LEVELS = Object.keys(LEVEL_PRIORITY);
11
+ function isLogLevel(value) {
12
+ return value in LEVEL_PRIORITY;
13
+ }
14
+ const USAGE = `Usage: xcode-copilot-server [options]
15
+
16
+ Options:
17
+ --port <number> Port to listen on (default: 8080)
18
+ --log-level <level> Log verbosity: ${VALID_LOG_LEVELS.join(", ")} (default: info)
19
+ --config <path> Path to config file (default: bundled config.json5)
20
+ --cwd <path> Working directory for Copilot sessions (default: process cwd)
21
+ --help Show this help message`;
22
+ function parseCliArgs() {
23
+ try {
24
+ return parseArgs({
25
+ options: {
26
+ port: { type: "string", default: "8080" },
27
+ "log-level": { type: "string", default: "info" },
28
+ config: { type: "string", default: DEFAULT_CONFIG_PATH },
29
+ cwd: { type: "string" },
30
+ help: { type: "boolean", default: false },
31
+ },
32
+ strict: true,
33
+ allowPositionals: false,
34
+ });
35
+ }
36
+ catch (err) {
37
+ console.error(String(err instanceof Error ? err.message : err));
38
+ console.error(`Run with --help for usage information.`);
39
+ process.exit(1);
40
+ }
41
+ }
42
+ async function main() {
43
+ const { values } = parseCliArgs();
44
+ if (values.help) {
45
+ console.log(USAGE);
46
+ process.exit(0);
47
+ }
48
+ const port = parseInt(values.port, 10);
49
+ if (isNaN(port) || port < 1 || port > 65535) {
50
+ console.error(`Invalid port "${values.port}". Must be 1-65535.`);
51
+ process.exit(1);
52
+ }
53
+ const rawLevel = values["log-level"];
54
+ if (!isLogLevel(rawLevel)) {
55
+ console.error(`Invalid log level "${rawLevel}". Valid: ${VALID_LOG_LEVELS.join(", ")}`);
56
+ process.exit(1);
57
+ }
58
+ const logLevel = rawLevel;
59
+ const logger = new Logger(logLevel);
60
+ const config = await loadConfig(values.config, logger);
61
+ const cwd = values.cwd;
62
+ const service = new CopilotService({
63
+ logLevel,
64
+ logger,
65
+ cwd,
66
+ });
67
+ logger.info("Booting up Copilot CLI...");
68
+ await service.start();
69
+ logger.info("Copilot CLI is up");
70
+ const ctx = { service, logger, config };
71
+ const app = await createServer(ctx);
72
+ await app.listen({ port, host: "127.0.0.1" });
73
+ logger.info(`Listening on http://localhost:${String(port)}`);
74
+ logger.info("Routes: GET /v1/models, POST /v1/chat/completions");
75
+ logger.info(`Current working directory: ${service.cwd}`);
76
+ const shutdown = async (signal) => {
77
+ logger.info(`Got ${signal}, shutting down...`);
78
+ await app.close();
79
+ const stopPromise = service.stop().then(() => {
80
+ logger.info("Clean shutdown complete");
81
+ });
82
+ const timeoutPromise = new Promise((resolve) => setTimeout(() => {
83
+ logger.warn("Copilot client didn't stop in time, forcing exit");
84
+ resolve();
85
+ }, 3000));
86
+ await Promise.race([stopPromise, timeoutPromise]);
87
+ process.exit(0);
88
+ };
89
+ process.on("SIGINT", () => void shutdown("SIGINT"));
90
+ process.on("SIGTERM", () => void shutdown("SIGTERM"));
91
+ }
92
+ main().catch((err) => {
93
+ console.error("Fatal error:", err);
94
+ process.exit(1);
95
+ });
96
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export declare const LEVEL_PRIORITY: {
2
+ readonly none: 0;
3
+ readonly error: 1;
4
+ readonly warning: 2;
5
+ readonly info: 3;
6
+ readonly debug: 4;
7
+ readonly all: 5;
8
+ };
9
+ export type LogLevel = keyof typeof LEVEL_PRIORITY;
10
+ export declare function truncate(value: unknown, maxLen?: number): string;
11
+ export declare class Logger {
12
+ readonly level: LogLevel;
13
+ private threshold;
14
+ constructor(level?: LogLevel);
15
+ error(msg: string, ...args: unknown[]): void;
16
+ warn(msg: string, ...args: unknown[]): void;
17
+ info(msg: string, ...args: unknown[]): void;
18
+ debug(msg: string, ...args: unknown[]): void;
19
+ }
package/dist/logger.js ADDED
@@ -0,0 +1,43 @@
1
+ export const LEVEL_PRIORITY = {
2
+ none: 0,
3
+ error: 1,
4
+ warning: 2,
5
+ info: 3,
6
+ debug: 4,
7
+ all: 5,
8
+ };
9
+ export function truncate(value, maxLen = 200) {
10
+ const s = typeof value === "string" ? value : JSON.stringify(value);
11
+ if (s.length <= maxLen)
12
+ return s;
13
+ return s.slice(0, maxLen) + "…";
14
+ }
15
+ export class Logger {
16
+ level;
17
+ threshold;
18
+ constructor(level = "info") {
19
+ this.level = level;
20
+ this.threshold = LEVEL_PRIORITY[level];
21
+ }
22
+ error(msg, ...args) {
23
+ if (this.threshold >= LEVEL_PRIORITY.error) {
24
+ console.error(`[ERROR] ${msg}`, ...args);
25
+ }
26
+ }
27
+ warn(msg, ...args) {
28
+ if (this.threshold >= LEVEL_PRIORITY.warning) {
29
+ console.warn(`[WARN] ${msg}`, ...args);
30
+ }
31
+ }
32
+ info(msg, ...args) {
33
+ if (this.threshold >= LEVEL_PRIORITY.info) {
34
+ console.log(`[INFO] ${msg}`, ...args);
35
+ }
36
+ }
37
+ debug(msg, ...args) {
38
+ if (this.threshold >= LEVEL_PRIORITY.debug) {
39
+ console.log(`[DEBUG] ${msg}`, ...args);
40
+ }
41
+ }
42
+ }
43
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;IACR,OAAO,EAAE,CAAC;IACV,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;IACR,GAAG,EAAE,CAAC;CACE,CAAC;AAIX,MAAM,UAAU,QAAQ,CAAC,KAAc,EAAE,MAAM,GAAG,GAAG;IACnD,MAAM,CAAC,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACpE,IAAI,CAAC,CAAC,MAAM,IAAI,MAAM;QAAE,OAAO,CAAC,CAAC;IACjC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC;AAClC,CAAC;AAED,MAAM,OAAO,MAAM;IACR,KAAK,CAAW;IACjB,SAAS,CAAS;IAE1B,YAAY,QAAkB,MAAM;QAClC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,GAAW,EAAE,GAAG,IAAe;QACnC,IAAI,IAAI,CAAC,SAAS,IAAI,cAAc,CAAC,KAAK,EAAE,CAAC;YAC3C,OAAO,CAAC,KAAK,CAAC,WAAW,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IAED,IAAI,CAAC,GAAW,EAAE,GAAG,IAAe;QAClC,IAAI,IAAI,CAAC,SAAS,IAAI,cAAc,CAAC,OAAO,EAAE,CAAC;YAC7C,OAAO,CAAC,IAAI,CAAC,UAAU,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,IAAI,CAAC,GAAW,EAAE,GAAG,IAAe;QAClC,IAAI,IAAI,CAAC,SAAS,IAAI,cAAc,CAAC,IAAI,EAAE,CAAC;YAC1C,OAAO,CAAC,GAAG,CAAC,UAAU,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAW,EAAE,GAAG,IAAe;QACnC,IAAI,IAAI,CAAC,SAAS,IAAI,cAAc,CAAC,KAAK,EAAE,CAAC;YAC3C,OAAO,CAAC,GAAG,CAAC,WAAW,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ import type { MessageContent } from "./types.js";
3
+ export declare const ChatCompletionRequestSchema: z.ZodObject<{
4
+ model: z.ZodString;
5
+ messages: z.ZodArray<z.ZodObject<{
6
+ role: z.ZodOptional<z.ZodEnum<{
7
+ system: "system";
8
+ developer: "developer";
9
+ user: "user";
10
+ assistant: "assistant";
11
+ tool: "tool";
12
+ }>>;
13
+ content: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodObject<{
14
+ type: z.ZodString;
15
+ text: z.ZodOptional<z.ZodString>;
16
+ }, z.core.$loose>>, z.ZodNull]>>;
17
+ name: z.ZodOptional<z.ZodString>;
18
+ tool_calls: z.ZodOptional<z.ZodArray<z.ZodObject<{
19
+ index: z.ZodOptional<z.ZodNumber>;
20
+ id: z.ZodOptional<z.ZodString>;
21
+ type: z.ZodOptional<z.ZodString>;
22
+ function: z.ZodObject<{
23
+ name: z.ZodString;
24
+ arguments: z.ZodString;
25
+ }, z.core.$strip>;
26
+ }, z.core.$strip>>>;
27
+ tool_call_id: z.ZodOptional<z.ZodString>;
28
+ }, z.core.$strip>>;
29
+ temperature: z.ZodOptional<z.ZodNumber>;
30
+ top_p: z.ZodOptional<z.ZodNumber>;
31
+ n: z.ZodOptional<z.ZodNumber>;
32
+ stop: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
33
+ max_tokens: z.ZodOptional<z.ZodNumber>;
34
+ presence_penalty: z.ZodOptional<z.ZodNumber>;
35
+ frequency_penalty: z.ZodOptional<z.ZodNumber>;
36
+ tools: z.ZodOptional<z.ZodArray<z.ZodObject<{
37
+ type: z.ZodString;
38
+ function: z.ZodObject<{
39
+ name: z.ZodString;
40
+ description: z.ZodOptional<z.ZodString>;
41
+ parameters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
42
+ }, z.core.$strip>;
43
+ }, z.core.$strip>>>;
44
+ tool_choice: z.ZodOptional<z.ZodUnknown>;
45
+ user: z.ZodOptional<z.ZodString>;
46
+ }, z.core.$strip>;
47
+ export declare function currentTimestamp(): number;
48
+ /** @throws {Error} on malformed or unsupported content types. */
49
+ export declare function extractContentText(content: MessageContent | undefined): string;
@@ -0,0 +1,75 @@
1
+ import { z } from "zod";
2
+ const ContentPartSchema = z.looseObject({
3
+ type: z.string(),
4
+ text: z.string().optional(),
5
+ });
6
+ const VALID_ROLES = ["system", "developer", "user", "assistant", "tool"];
7
+ const MessageSchema = z.object({
8
+ role: z.enum(VALID_ROLES).optional(),
9
+ content: z
10
+ .union([z.string(), z.array(ContentPartSchema), z.null()])
11
+ .optional(),
12
+ name: z.string().optional(),
13
+ tool_calls: z
14
+ .array(z.object({
15
+ index: z.number().optional(),
16
+ id: z.string().optional(),
17
+ type: z.string().optional(),
18
+ function: z.object({
19
+ name: z.string(),
20
+ arguments: z.string(),
21
+ }),
22
+ }))
23
+ .optional(),
24
+ tool_call_id: z.string().optional(),
25
+ });
26
+ export const ChatCompletionRequestSchema = z.object({
27
+ model: z.string().min(1, "Model is required"),
28
+ messages: z.array(MessageSchema).min(1, "Messages are required"),
29
+ temperature: z.number().optional(),
30
+ top_p: z.number().optional(),
31
+ n: z.number().optional(),
32
+ stop: z.union([z.string(), z.array(z.string())]).optional(),
33
+ max_tokens: z.number().optional(),
34
+ presence_penalty: z.number().optional(),
35
+ frequency_penalty: z.number().optional(),
36
+ tools: z
37
+ .array(z.object({
38
+ type: z.string(),
39
+ function: z.object({
40
+ name: z.string(),
41
+ description: z.string().optional(),
42
+ parameters: z.record(z.string(), z.unknown()).optional(),
43
+ }),
44
+ }))
45
+ .optional(),
46
+ tool_choice: z.unknown().optional(),
47
+ user: z.string().optional(),
48
+ });
49
+ export function currentTimestamp() {
50
+ return Math.floor(Date.now() / 1000);
51
+ }
52
+ /** @throws {Error} on malformed or unsupported content types. */
53
+ export function extractContentText(content) {
54
+ if (content == null) {
55
+ return "";
56
+ }
57
+ if (typeof content === "string") {
58
+ return content;
59
+ }
60
+ if (!Array.isArray(content)) {
61
+ throw new Error(`invalid content type: expected string or array, got ${typeof content}`);
62
+ }
63
+ let text = "";
64
+ for (const part of content) {
65
+ if (part.type !== "text") {
66
+ throw new Error(`unsupported content type: ${part.type}`);
67
+ }
68
+ if (typeof part.text !== "string") {
69
+ throw new Error("text content part missing required 'text' field");
70
+ }
71
+ text += part.text;
72
+ }
73
+ return text;
74
+ }
75
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { type FastifyInstance } from "fastify";
2
+ import type { AppContext } from "./context.js";
3
+ export declare function createServer(ctx: AppContext): Promise<FastifyInstance>;
package/dist/server.js ADDED
@@ -0,0 +1,41 @@
1
+ import Fastify from "fastify";
2
+ import cors from "@fastify/cors";
3
+ import { createModelsHandler } from "./handlers/models.js";
4
+ import { createCompletionsHandler } from "./handlers/completions.js";
5
+ const PINO_LEVEL = {
6
+ none: "silent",
7
+ error: "error",
8
+ warning: "warn",
9
+ info: "info",
10
+ debug: "debug",
11
+ all: "trace",
12
+ };
13
+ export async function createServer(ctx) {
14
+ const app = Fastify({
15
+ bodyLimit: ctx.config.bodyLimit,
16
+ logger: {
17
+ level: PINO_LEVEL[ctx.logger.level],
18
+ },
19
+ });
20
+ await app.register(cors, {
21
+ origin: "*",
22
+ methods: ["GET", "POST", "OPTIONS"],
23
+ allowedHeaders: ["Content-Type", "Authorization"],
24
+ });
25
+ app.addHook("onRequest", (request, reply, done) => {
26
+ const ua = request.headers["user-agent"] ?? "";
27
+ if (!ua.startsWith("Xcode/")) {
28
+ ctx.logger.warn(`Rejected request from unexpected user-agent: ${ua}`);
29
+ void reply
30
+ .code(403)
31
+ .type("application/json")
32
+ .send('{"error":"Forbidden"}\n');
33
+ return;
34
+ }
35
+ done();
36
+ });
37
+ app.get("/v1/models", createModelsHandler(ctx));
38
+ app.post("/v1/chat/completions", createCompletionsHandler(ctx));
39
+ return app;
40
+ }
41
+ //# sourceMappingURL=server.js.map