@dianshuv/copilot-api 0.4.0 → 0.4.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 (2) hide show
  1. package/dist/main.mjs +224 -29
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -1020,7 +1020,7 @@ const patchClaude = defineCommand({
1020
1020
 
1021
1021
  //#endregion
1022
1022
  //#region package.json
1023
- var version = "0.4.0";
1023
+ var version = "0.4.1";
1024
1024
 
1025
1025
  //#endregion
1026
1026
  //#region src/lib/adaptive-rate-limiter.ts
@@ -7445,50 +7445,229 @@ const containsVisionContent = (value) => {
7445
7445
  if (Array.isArray(record.content)) return record.content.some((entry) => containsVisionContent(entry));
7446
7446
  return false;
7447
7447
  };
7448
+ /** Convert Responses API input to history MessageContent format */
7449
+ function convertResponsesInputToMessages(input) {
7450
+ if (!input) return [];
7451
+ if (typeof input === "string") return [{
7452
+ role: "user",
7453
+ content: input
7454
+ }];
7455
+ const messages = [];
7456
+ for (const item of input) {
7457
+ const record = item;
7458
+ switch (record.type) {
7459
+ case "function_call": {
7460
+ const fc = item;
7461
+ messages.push({
7462
+ role: "assistant",
7463
+ content: "",
7464
+ tool_calls: [{
7465
+ id: fc.call_id,
7466
+ type: "function",
7467
+ function: {
7468
+ name: fc.name,
7469
+ arguments: fc.arguments
7470
+ }
7471
+ }]
7472
+ });
7473
+ break;
7474
+ }
7475
+ case "function_call_output": {
7476
+ const fco = item;
7477
+ messages.push({
7478
+ role: "tool",
7479
+ content: typeof fco.output === "string" ? fco.output : JSON.stringify(fco.output),
7480
+ tool_call_id: fco.call_id
7481
+ });
7482
+ break;
7483
+ }
7484
+ case "reasoning": break;
7485
+ default: if ("role" in record) {
7486
+ const msg = item;
7487
+ messages.push({
7488
+ role: msg.role,
7489
+ content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
7490
+ });
7491
+ }
7492
+ }
7493
+ }
7494
+ return messages;
7495
+ }
7496
+ /** Convert Responses API tools to history ToolDefinition format */
7497
+ function convertResponsesToolsToDefinitions(tools) {
7498
+ if (!tools) return [];
7499
+ return tools.filter((t) => t.type === "function").map((t) => {
7500
+ const ft = t;
7501
+ const def = { name: ft.name };
7502
+ if (ft.description) def.description = ft.description;
7503
+ return def;
7504
+ });
7505
+ }
7506
+ /** Extract response content and tool calls from ResponsesResult output in a single pass */
7507
+ function extractResponseData(result) {
7508
+ if (result.output.length === 0) return {
7509
+ content: null,
7510
+ toolCalls: void 0
7511
+ };
7512
+ let text = "";
7513
+ const contentToolCalls = [];
7514
+ const historyToolCalls = [];
7515
+ for (const item of result.output) if (item.type === "message" && "content" in item && item.content) {
7516
+ for (const block of item.content) if ("text" in block && typeof block.text === "string") text += block.text;
7517
+ else if ("refusal" in block && typeof block.refusal === "string") text += block.refusal;
7518
+ } else if (item.type === "function_call") {
7519
+ const fc = item;
7520
+ contentToolCalls.push({
7521
+ id: fc.call_id,
7522
+ type: "function",
7523
+ function: {
7524
+ name: fc.name,
7525
+ arguments: fc.arguments
7526
+ }
7527
+ });
7528
+ historyToolCalls.push({
7529
+ id: fc.call_id,
7530
+ name: fc.name,
7531
+ input: fc.arguments
7532
+ });
7533
+ }
7534
+ if (!text && contentToolCalls.length === 0) return {
7535
+ content: null,
7536
+ toolCalls: void 0
7537
+ };
7538
+ const content = {
7539
+ role: "assistant",
7540
+ content: text
7541
+ };
7542
+ if (contentToolCalls.length > 0) content.tool_calls = contentToolCalls;
7543
+ return {
7544
+ content,
7545
+ toolCalls: historyToolCalls.length > 0 ? historyToolCalls : void 0
7546
+ };
7547
+ }
7548
+ /** Map ResponsesResult.status to a stop_reason string */
7549
+ function extractResponseStopReason(result) {
7550
+ switch (result.status) {
7551
+ case "completed": return "stop";
7552
+ case "incomplete": return "length";
7553
+ case "failed": return "error";
7554
+ default: return result.status;
7555
+ }
7556
+ }
7448
7557
 
7449
7558
  //#endregion
7450
7559
  //#region src/routes/responses/handler.ts
7451
7560
  const RESPONSES_ENDPOINT = "/responses";
7561
+ const TERMINAL_EVENTS = new Set([
7562
+ "response.completed",
7563
+ "response.incomplete",
7564
+ "response.failed",
7565
+ "error"
7566
+ ]);
7452
7567
  const handleResponses = async (c) => {
7453
7568
  const payload = await c.req.json();
7454
7569
  consola.debug("Responses request payload:", JSON.stringify(payload));
7455
7570
  const trackingId = c.get("trackingId");
7571
+ const startTime = (trackingId ? requestTracker.getRequest(trackingId) : void 0)?.startTime ?? Date.now();
7456
7572
  updateTrackerModel(trackingId, payload.model);
7457
7573
  useFunctionApplyPatch(payload);
7458
7574
  removeWebSearchTool(payload);
7459
- if (!((state.models?.data.find((model) => model.id === payload.model))?.supported_endpoints?.includes(RESPONSES_ENDPOINT) ?? false)) return c.json({ error: {
7460
- message: "This model does not support the responses endpoint. Please choose a different model.",
7461
- type: "invalid_request_error"
7462
- } }, 400);
7575
+ const model = payload.model;
7576
+ const stream = payload.stream ?? false;
7577
+ const tools = convertResponsesToolsToDefinitions(payload.tools);
7578
+ const historyId = recordRequest("openai", {
7579
+ model,
7580
+ messages: convertResponsesInputToMessages(payload.input),
7581
+ stream,
7582
+ tools: tools.length > 0 ? tools : void 0,
7583
+ max_tokens: payload.max_output_tokens ?? void 0,
7584
+ temperature: payload.temperature ?? void 0,
7585
+ system: payload.instructions ?? void 0
7586
+ });
7587
+ const ctx = {
7588
+ historyId,
7589
+ trackingId,
7590
+ startTime
7591
+ };
7592
+ if (!((state.models?.data.find((m) => m.id === payload.model))?.supported_endpoints?.includes(RESPONSES_ENDPOINT) ?? false)) {
7593
+ recordErrorResponse(ctx, model, /* @__PURE__ */ new Error("This model does not support the responses endpoint."));
7594
+ return c.json({ error: {
7595
+ message: "This model does not support the responses endpoint. Please choose a different model.",
7596
+ type: "invalid_request_error"
7597
+ } }, 400);
7598
+ }
7463
7599
  const { vision, initiator } = getResponsesRequestOptions(payload);
7464
7600
  if (state.manualApprove) await awaitApproval();
7465
- const { result: response } = await executeWithAdaptiveRateLimit(() => createResponses(payload, {
7466
- vision,
7467
- initiator
7468
- }));
7469
- if (isStreamingRequested(payload) && isAsyncIterable(response)) {
7470
- consola.debug("Forwarding native Responses stream");
7471
- return streamSSE(c, async (stream) => {
7472
- const idTracker = createStreamIdTracker();
7473
- try {
7474
- for await (const chunk of response) {
7475
- consola.debug("Responses stream chunk:", JSON.stringify(chunk));
7476
- const processedData = fixStreamIds(chunk.data ?? "", chunk.event, idTracker);
7477
- await stream.writeSSE({
7478
- id: chunk.id,
7479
- event: chunk.event,
7480
- data: processedData
7601
+ try {
7602
+ const { result: response, queueWaitMs } = await executeWithAdaptiveRateLimit(() => createResponses(payload, {
7603
+ vision,
7604
+ initiator
7605
+ }));
7606
+ ctx.queueWaitMs = queueWaitMs;
7607
+ if (isStreamingRequested(payload) && isAsyncIterable(response)) {
7608
+ consola.debug("Forwarding native Responses stream");
7609
+ updateTrackerStatus(trackingId, "streaming");
7610
+ return streamSSE(c, async (stream) => {
7611
+ const idTracker = createStreamIdTracker();
7612
+ let finalResult;
7613
+ let streamErrorMessage;
7614
+ try {
7615
+ for await (const chunk of response) {
7616
+ consola.debug("Responses stream chunk:", JSON.stringify(chunk));
7617
+ const eventType = chunk.event;
7618
+ const rawData = chunk.data ?? "";
7619
+ if (eventType && TERMINAL_EVENTS.has(eventType)) try {
7620
+ const parsed = JSON.parse(rawData);
7621
+ if ("response" in parsed) finalResult = parsed.response;
7622
+ else if (eventType === "error" && "message" in parsed) streamErrorMessage = parsed.message;
7623
+ } catch {}
7624
+ const processedData = fixStreamIds(rawData, eventType, idTracker);
7625
+ await stream.writeSSE({
7626
+ id: chunk.id,
7627
+ event: eventType,
7628
+ data: processedData
7629
+ });
7630
+ }
7631
+ if (finalResult) {
7632
+ recordResponseResult(finalResult, model, historyId, startTime);
7633
+ const usage = finalResult.usage;
7634
+ completeTracking(trackingId, usage?.input_tokens ?? 0, usage?.output_tokens ?? 0, queueWaitMs);
7635
+ } else if (streamErrorMessage) {
7636
+ recordResponse(historyId, {
7637
+ success: false,
7638
+ model,
7639
+ usage: {
7640
+ input_tokens: 0,
7641
+ output_tokens: 0
7642
+ },
7643
+ error: streamErrorMessage,
7644
+ content: null
7645
+ }, Date.now() - startTime);
7646
+ completeTracking(trackingId, 0, 0, queueWaitMs);
7647
+ } else completeTracking(trackingId, 0, 0, queueWaitMs);
7648
+ } catch (error) {
7649
+ recordStreamError({
7650
+ acc: { model: finalResult?.model || model },
7651
+ fallbackModel: model,
7652
+ ctx,
7653
+ error
7481
7654
  });
7655
+ failTracking(trackingId, error);
7656
+ throw error;
7482
7657
  }
7483
- completeTracking(trackingId, 0, 0);
7484
- } catch (error) {
7485
- failTracking(trackingId, error);
7486
- throw error;
7487
- }
7488
- });
7658
+ });
7659
+ }
7660
+ const result = response;
7661
+ const usage = result.usage;
7662
+ recordResponseResult(result, model, historyId, startTime);
7663
+ completeTracking(trackingId, usage?.input_tokens ?? 0, usage?.output_tokens ?? 0, ctx.queueWaitMs);
7664
+ consola.debug("Forwarding native Responses result:", JSON.stringify(result).slice(-400));
7665
+ return c.json(result);
7666
+ } catch (error) {
7667
+ recordErrorResponse(ctx, model, error);
7668
+ failTracking(trackingId, error);
7669
+ throw error;
7489
7670
  }
7490
- consola.debug("Forwarding native Responses result:", JSON.stringify(response).slice(-400));
7491
- return c.json(response);
7492
7671
  };
7493
7672
  const isAsyncIterable = (value) => Boolean(value) && typeof value[Symbol.asyncIterator] === "function";
7494
7673
  const isStreamingRequested = (payload) => Boolean(payload.stream);
@@ -7523,6 +7702,22 @@ const removeWebSearchTool = (payload) => {
7523
7702
  return t.type !== "web_search";
7524
7703
  });
7525
7704
  };
7705
+ /** Record a ResponsesResult to history */
7706
+ function recordResponseResult(result, fallbackModel, historyId, startTime) {
7707
+ const usage = result.usage;
7708
+ const { content, toolCalls } = extractResponseData(result);
7709
+ recordResponse(historyId, {
7710
+ success: result.status !== "failed",
7711
+ model: result.model || fallbackModel,
7712
+ usage: {
7713
+ input_tokens: usage?.input_tokens ?? 0,
7714
+ output_tokens: usage?.output_tokens ?? 0
7715
+ },
7716
+ stop_reason: extractResponseStopReason(result),
7717
+ content,
7718
+ toolCalls
7719
+ }, Date.now() - startTime);
7720
+ }
7526
7721
 
7527
7722
  //#endregion
7528
7723
  //#region src/routes/responses/route.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dianshuv/copilot-api",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Turn GitHub Copilot into OpenAI/Anthropic API compatible server. Usable with Claude Code!",
5
5
  "author": "dianshuv",
6
6
  "type": "module",