@jeffreycao/copilot-api 1.1.3 → 1.1.5

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.
package/README.md CHANGED
@@ -357,7 +357,6 @@ Here is an example `.claude/settings.json` file:
357
357
  "ANTHROPIC_MODEL": "gpt-5.2",
358
358
  "ANTHROPIC_DEFAULT_SONNET_MODEL": "gpt-5.2",
359
359
  "ANTHROPIC_DEFAULT_HAIKU_MODEL": "gpt-5-mini",
360
- "CLAUDE_CODE_SUBAGENT_MODEL": "gpt-5-mini",
361
360
  "DISABLE_NON_ESSENTIAL_MODEL_CALLS": "1",
362
361
  "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
363
362
  "BASH_MAX_TIMEOUT_MS": "600000",
@@ -376,6 +375,45 @@ You can find more options here: [Claude Code settings](https://docs.anthropic.co
376
375
 
377
376
  You can also read more about IDE integration here: [Add Claude Code to your IDE](https://docs.anthropic.com/en/docs/claude-code/ide-integrations)
378
377
 
378
+ ### Subagent Marker Integration (Optional)
379
+
380
+ This project supports `X-Initiator: agent` for subagent-originated requests
381
+
382
+ #### Claude Code hook producer
383
+
384
+ Use the included hook script to inject marker context on `SubagentStart`.
385
+ If you place the script under your user Claude directory (`~/.claude/hooks`), use this cross-platform command in `.claude/settings.json`:
386
+
387
+ - `.claude/hooks/subagent-start-marker.js`
388
+
389
+ And enable it from `.claude/settings.json`:
390
+
391
+ ```json
392
+ {
393
+ "hooks": {
394
+ "SubagentStart": [
395
+ {
396
+ "matcher": "*",
397
+ "hooks": [
398
+ {
399
+ "type": "command",
400
+ "command": "node --input-type=module -e \"import { homedir } from 'node:os'; import { join } from 'node:path'; import { readFile } from 'node:fs/promises'; const file = join(homedir(), '.claude', 'hooks', 'subagent-start-marker.js'); const source = await readFile(file, 'utf8'); const url = 'data:text/javascript;base64,' + Buffer.from(source).toString('base64'); await import(url);\""
401
+ }
402
+ ]
403
+ }
404
+ ]
405
+ }
406
+ }
407
+ ```
408
+
409
+ #### Opencode plugin producer
410
+
411
+ For opencode, use the plugin implementation at:
412
+
413
+ - `.opencode/plugins/subagent-marker.js`
414
+
415
+ This plugin tracks sub-sessions and prepends a marker system reminder to subagent chat messages.
416
+
379
417
  ## Running from Source
380
418
 
381
419
  The project can be run from source in several ways:
@@ -44,7 +44,7 @@ const standardHeaders = () => ({
44
44
  "content-type": "application/json",
45
45
  accept: "application/json"
46
46
  });
47
- const COPILOT_VERSION = "0.37.4";
47
+ const COPILOT_VERSION = "0.37.6";
48
48
  const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`;
49
49
  const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`;
50
50
  const API_VERSION = "2025-10-01";
@@ -120,7 +120,7 @@ const getModels = async () => {
120
120
 
121
121
  //#endregion
122
122
  //#region src/services/get-vscode-version.ts
123
- const FALLBACK = "1.109.2";
123
+ const FALLBACK = "1.109.3";
124
124
  async function getVSCodeVersion() {
125
125
  const controller = new AbortController();
126
126
  const timeout = setTimeout(() => {
@@ -128,10 +128,7 @@ async function getVSCodeVersion() {
128
128
  }, 5e3);
129
129
  try {
130
130
  const match = (await (await fetch("https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=visual-studio-code-bin", { signal: controller.signal })).text()).match(/pkgver=([0-9.]+)/);
131
- if (match) {
132
- if (match[1] === "1.109.0") return FALLBACK;
133
- return match[1];
134
- }
131
+ if (match) return match[1];
135
132
  return FALLBACK;
136
133
  } catch {
137
134
  return FALLBACK;
@@ -279,4 +276,4 @@ function shouldCompactUseSmallModel() {
279
276
 
280
277
  //#endregion
281
278
  export { GITHUB_API_BASE_URL, GITHUB_APP_SCOPES, GITHUB_BASE_URL, GITHUB_CLIENT_ID, HTTPError, PATHS, cacheModels, cacheVSCodeVersion, copilotBaseUrl, copilotHeaders, ensurePaths, forwardError, getConfig, getCopilotUsage, getExtraPromptForModel, getReasoningEffortForModel, getSmallModel, githubHeaders, isNullish, mergeConfigWithDefaults, shouldCompactUseSmallModel, sleep, standardHeaders, state };
282
- //# sourceMappingURL=config-Cx3UwWih.js.map
279
+ //# sourceMappingURL=config-CIuwVH6R.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-CIuwVH6R.js","names":["state: State","state","headers: Record<string, string>","errorJson: unknown","defaultConfig: AppConfig","cachedConfig: AppConfig | null","fs"],"sources":["../src/lib/paths.ts","../src/lib/state.ts","../src/lib/api-config.ts","../src/lib/error.ts","../src/services/copilot/get-models.ts","../src/services/get-vscode-version.ts","../src/lib/utils.ts","../src/services/github/get-copilot-usage.ts","../src/lib/config.ts"],"sourcesContent":["import fs from \"node:fs/promises\"\nimport os from \"node:os\"\nimport path from \"node:path\"\n\nconst APP_DIR = path.join(os.homedir(), \".local\", \"share\", \"copilot-api\")\n\nconst GITHUB_TOKEN_PATH = path.join(APP_DIR, \"github_token\")\nconst CONFIG_PATH = path.join(APP_DIR, \"config.json\")\n\nexport const PATHS = {\n APP_DIR,\n GITHUB_TOKEN_PATH,\n CONFIG_PATH,\n}\n\nexport async function ensurePaths(): Promise<void> {\n await fs.mkdir(PATHS.APP_DIR, { recursive: true })\n await ensureFile(PATHS.GITHUB_TOKEN_PATH)\n await ensureFile(PATHS.CONFIG_PATH)\n}\n\nasync function ensureFile(filePath: string): Promise<void> {\n try {\n await fs.access(filePath, fs.constants.W_OK)\n } catch {\n await fs.writeFile(filePath, \"\")\n await fs.chmod(filePath, 0o600)\n }\n}\n","import type { ModelsResponse } from \"~/services/copilot/get-models\"\n\nexport interface State {\n githubToken?: string\n copilotToken?: string\n\n accountType: string\n models?: ModelsResponse\n vsCodeVersion?: string\n\n manualApprove: boolean\n rateLimitWait: boolean\n showToken: boolean\n\n // Rate limiting configuration\n rateLimitSeconds?: number\n lastRequestTimestamp?: number\n verbose: boolean\n}\n\nexport const state: State = {\n accountType: \"individual\",\n manualApprove: false,\n rateLimitWait: false,\n showToken: false,\n verbose: false,\n}\n","import { randomUUID } from \"node:crypto\"\n\nimport type { State } from \"./state\"\n\nexport const standardHeaders = () => ({\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n})\n\nconst COPILOT_VERSION = \"0.37.6\"\nconst EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`\nconst USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`\n\nconst API_VERSION = \"2025-10-01\"\n\nexport const copilotBaseUrl = (state: State) =>\n state.accountType === \"individual\" ?\n \"https://api.githubcopilot.com\"\n : `https://api.${state.accountType}.githubcopilot.com`\nexport const copilotHeaders = (state: State, vision: boolean = false) => {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${state.copilotToken}`,\n \"content-type\": standardHeaders()[\"content-type\"],\n \"copilot-integration-id\": \"vscode-chat\",\n \"editor-version\": `vscode/${state.vsCodeVersion}`,\n \"editor-plugin-version\": EDITOR_PLUGIN_VERSION,\n \"user-agent\": USER_AGENT,\n \"openai-intent\": \"conversation-agent\",\n \"x-github-api-version\": API_VERSION,\n \"x-request-id\": randomUUID(),\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n }\n\n if (vision) headers[\"copilot-vision-request\"] = \"true\"\n\n return headers\n}\n\nexport const GITHUB_API_BASE_URL = \"https://api.github.com\"\nexport const githubHeaders = (state: State) => ({\n ...standardHeaders(),\n authorization: `token ${state.githubToken}`,\n \"editor-version\": `vscode/${state.vsCodeVersion}`,\n \"editor-plugin-version\": EDITOR_PLUGIN_VERSION,\n \"user-agent\": USER_AGENT,\n \"x-github-api-version\": API_VERSION,\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n})\n\nexport const GITHUB_BASE_URL = \"https://github.com\"\nexport const GITHUB_CLIENT_ID = \"Iv1.b507a08c87ecfe98\"\nexport const GITHUB_APP_SCOPES = [\"read:user\"].join(\" \")\n","import type { Context } from \"hono\"\nimport type { ContentfulStatusCode } from \"hono/utils/http-status\"\n\nimport consola from \"consola\"\n\nexport class HTTPError extends Error {\n response: Response\n\n constructor(message: string, response: Response) {\n super(message)\n this.response = response\n }\n}\n\nexport async function forwardError(c: Context, error: unknown) {\n consola.error(\"Error occurred:\", error)\n\n if (error instanceof HTTPError) {\n const errorText = await error.response.text()\n let errorJson: unknown\n try {\n errorJson = JSON.parse(errorText)\n } catch {\n errorJson = errorText\n }\n consola.error(\"HTTP error:\", errorJson)\n return c.json(\n {\n error: {\n message: errorText,\n type: \"error\",\n },\n },\n error.response.status as ContentfulStatusCode,\n )\n }\n\n return c.json(\n {\n error: {\n message: (error as Error).message,\n type: \"error\",\n },\n },\n 500,\n )\n}\n","import { copilotBaseUrl, copilotHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const getModels = async () => {\n const response = await fetch(`${copilotBaseUrl(state)}/models`, {\n headers: copilotHeaders(state),\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to get models\", response)\n\n return (await response.json()) as ModelsResponse\n}\n\nexport interface ModelsResponse {\n data: Array<Model>\n object: string\n}\n\ninterface ModelLimits {\n max_context_window_tokens?: number\n max_output_tokens?: number\n max_prompt_tokens?: number\n max_inputs?: number\n}\n\ninterface ModelSupports {\n max_thinking_budget?: number\n min_thinking_budget?: number\n tool_calls?: boolean\n parallel_tool_calls?: boolean\n dimensions?: boolean\n streaming?: boolean\n structured_outputs?: boolean\n vision?: boolean\n adaptive_thinking?: boolean\n}\n\ninterface ModelCapabilities {\n family: string\n limits: ModelLimits\n object: string\n supports: ModelSupports\n tokenizer: string\n type: string\n}\n\nexport interface Model {\n capabilities: ModelCapabilities\n id: string\n model_picker_enabled: boolean\n name: string\n object: string\n preview: boolean\n vendor: string\n version: string\n policy?: {\n state: string\n terms: string\n }\n supported_endpoints?: Array<string>\n}\n","const FALLBACK = \"1.109.3\"\n\nexport async function getVSCodeVersion() {\n const controller = new AbortController()\n const timeout = setTimeout(() => {\n controller.abort()\n }, 5000)\n\n try {\n const response = await fetch(\n \"https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=visual-studio-code-bin\",\n {\n signal: controller.signal,\n },\n )\n\n const pkgbuild = await response.text()\n const pkgverRegex = /pkgver=([0-9.]+)/\n const match = pkgbuild.match(pkgverRegex)\n\n if (match) {\n return match[1]\n }\n\n return FALLBACK\n } catch {\n return FALLBACK\n } finally {\n clearTimeout(timeout)\n }\n}\n\nawait getVSCodeVersion()\n","import consola from \"consola\"\n\nimport { getModels } from \"~/services/copilot/get-models\"\nimport { getVSCodeVersion } from \"~/services/get-vscode-version\"\n\nimport { state } from \"./state\"\n\nexport const sleep = (ms: number) =>\n new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n\nexport const isNullish = (value: unknown): value is null | undefined =>\n value === null || value === undefined\n\nexport async function cacheModels(): Promise<void> {\n const models = await getModels()\n state.models = models\n}\n\nexport const cacheVSCodeVersion = async () => {\n const response = await getVSCodeVersion()\n state.vsCodeVersion = response\n\n consola.info(`Using VSCode version: ${response}`)\n}\n","import { GITHUB_API_BASE_URL, githubHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const getCopilotUsage = async (): Promise<CopilotUsageResponse> => {\n const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, {\n headers: githubHeaders(state),\n })\n\n if (!response.ok) {\n throw new HTTPError(\"Failed to get Copilot usage\", response)\n }\n\n return (await response.json()) as CopilotUsageResponse\n}\n\nexport interface QuotaDetail {\n entitlement: number\n overage_count: number\n overage_permitted: boolean\n percent_remaining: number\n quota_id: string\n quota_remaining: number\n remaining: number\n unlimited: boolean\n}\n\ninterface QuotaSnapshots {\n chat: QuotaDetail\n completions: QuotaDetail\n premium_interactions: QuotaDetail\n}\n\ninterface CopilotUsageResponse {\n access_type_sku: string\n analytics_tracking_id: string\n assigned_date: string\n can_signup_for_limited: boolean\n chat_enabled: boolean\n copilot_plan: string\n organization_login_list: Array<unknown>\n organization_list: Array<unknown>\n quota_reset_date: string\n quota_snapshots: QuotaSnapshots\n}\n","import consola from \"consola\"\nimport fs from \"node:fs\"\n\nimport { PATHS } from \"./paths\"\n\nexport interface AppConfig {\n auth?: {\n apiKeys?: Array<string>\n }\n extraPrompts?: Record<string, string>\n smallModel?: string\n modelReasoningEfforts?: Record<\n string,\n \"none\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\"\n >\n useFunctionApplyPatch?: boolean\n compactUseSmallModel?: boolean\n}\n\nconst gpt5ExplorationPrompt = `## Exploration and reading files\n- **Think first.** Before any tool call, decide ALL files/resources you will need.\n- **Batch everything.** If you need multiple files (even from different places), read them together.\n- **multi_tool_use.parallel** Use multi_tool_use.parallel to parallelize tool calls and only this.\n- **Only make sequential calls if you truly cannot know the next file without seeing a result first.**\n- **Workflow:** (a) plan all needed reads → (b) issue one parallel batch → (c) analyze results → (d) repeat if new, unpredictable reads arise.`\n\nconst gpt5CommentaryPrompt = `# Working with the user\n\nYou interact with the user through a terminal. You have 2 ways of communicating with the users: \n- Share intermediary updates in \\`commentary\\` channel. \n- After you have completed all your work, send a message to the \\`final\\` channel. \n\n## Intermediary updates\n\n- Intermediary updates go to the \\`commentary\\` channel.\n- User updates are short updates while you are working, they are NOT final answers.\n- You use 1-2 sentence user updates to communicate progress and new information to the user as you are doing work.\n- Do not begin responses with conversational interjections or meta commentary. Avoid openers such as acknowledgements (“Done —”, “Got it”, “Great question, ”) or framing phrases.\n- You provide user updates frequently, every 20s.\n- Before exploring or doing substantial work, you start with a user update acknowledging the request and explaining your first step. You should include your understanding of the user request and explain what you will do. Avoid commenting on the request or using starters such as \"Got it -\" or \"Understood -\" etc.\n- When exploring, e.g. searching, reading files, you provide user updates as you go, every 20s, explaining what context you are gathering and what you've learned. Vary your sentence structure when providing these updates to avoid sounding repetitive - in particular, don't start each sentence the same way.\n- After you have sufficient context, and the work is substantial, you provide a longer plan (this is the only user update that may be longer than 2 sentences and can contain formatting).\n- Before performing file edits of any kind, you provide updates explaining what edits you are making.\n- As you are thinking, you very frequently provide updates even if not taking any actions, informing the user of your progress. You interrupt your thinking and send multiple updates in a row if thinking for more than 100 words.\n- Tone of your updates MUST match your personality.`\n\nconst defaultConfig: AppConfig = {\n auth: {\n apiKeys: [],\n },\n extraPrompts: {\n \"gpt-5-mini\": gpt5ExplorationPrompt,\n \"gpt-5.1-codex-max\": gpt5ExplorationPrompt,\n \"gpt-5.3-codex\": gpt5CommentaryPrompt,\n },\n smallModel: \"gpt-5-mini\",\n modelReasoningEfforts: {\n \"gpt-5-mini\": \"low\",\n },\n useFunctionApplyPatch: true,\n compactUseSmallModel: true,\n}\n\nlet cachedConfig: AppConfig | null = null\n\nfunction ensureConfigFile(): void {\n try {\n fs.accessSync(PATHS.CONFIG_PATH, fs.constants.R_OK | fs.constants.W_OK)\n } catch {\n fs.mkdirSync(PATHS.APP_DIR, { recursive: true })\n fs.writeFileSync(\n PATHS.CONFIG_PATH,\n `${JSON.stringify(defaultConfig, null, 2)}\\n`,\n \"utf8\",\n )\n try {\n fs.chmodSync(PATHS.CONFIG_PATH, 0o600)\n } catch {\n return\n }\n }\n}\n\nfunction readConfigFromDisk(): AppConfig {\n ensureConfigFile()\n try {\n const raw = fs.readFileSync(PATHS.CONFIG_PATH, \"utf8\")\n if (!raw.trim()) {\n fs.writeFileSync(\n PATHS.CONFIG_PATH,\n `${JSON.stringify(defaultConfig, null, 2)}\\n`,\n \"utf8\",\n )\n return defaultConfig\n }\n return JSON.parse(raw) as AppConfig\n } catch (error) {\n consola.error(\"Failed to read config file, using default config\", error)\n return defaultConfig\n }\n}\n\nfunction mergeDefaultExtraPrompts(config: AppConfig): {\n mergedConfig: AppConfig\n changed: boolean\n} {\n const extraPrompts = config.extraPrompts ?? {}\n const defaultExtraPrompts = defaultConfig.extraPrompts ?? {}\n\n const missingExtraPromptModels = Object.keys(defaultExtraPrompts).filter(\n (model) => !Object.hasOwn(extraPrompts, model),\n )\n\n if (missingExtraPromptModels.length === 0) {\n return { mergedConfig: config, changed: false }\n }\n\n return {\n mergedConfig: {\n ...config,\n extraPrompts: {\n ...defaultExtraPrompts,\n ...extraPrompts,\n },\n },\n changed: true,\n }\n}\n\nexport function mergeConfigWithDefaults(): AppConfig {\n const config = readConfigFromDisk()\n const { mergedConfig, changed } = mergeDefaultExtraPrompts(config)\n\n if (changed) {\n try {\n fs.writeFileSync(\n PATHS.CONFIG_PATH,\n `${JSON.stringify(mergedConfig, null, 2)}\\n`,\n \"utf8\",\n )\n } catch (writeError) {\n consola.warn(\n \"Failed to write merged extraPrompts to config file\",\n writeError,\n )\n }\n }\n\n cachedConfig = mergedConfig\n return mergedConfig\n}\n\nexport function getConfig(): AppConfig {\n cachedConfig ??= readConfigFromDisk()\n return cachedConfig\n}\n\nexport function getExtraPromptForModel(model: string): string {\n const config = getConfig()\n return config.extraPrompts?.[model] ?? \"\"\n}\n\nexport function getSmallModel(): string {\n const config = getConfig()\n return config.smallModel ?? \"gpt-5-mini\"\n}\n\nexport function getReasoningEffortForModel(\n model: string,\n): \"none\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" {\n const config = getConfig()\n return config.modelReasoningEfforts?.[model] ?? \"high\"\n}\n\nexport function shouldCompactUseSmallModel(): boolean {\n const config = getConfig()\n return config.compactUseSmallModel ?? true\n}\n"],"mappings":"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"}
package/dist/main.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { GITHUB_API_BASE_URL, GITHUB_APP_SCOPES, GITHUB_BASE_URL, GITHUB_CLIENT_ID, HTTPError, PATHS, cacheModels, cacheVSCodeVersion, ensurePaths, getCopilotUsage, githubHeaders, mergeConfigWithDefaults, sleep, standardHeaders, state } from "./config-Cx3UwWih.js";
2
+ import { GITHUB_API_BASE_URL, GITHUB_APP_SCOPES, GITHUB_BASE_URL, GITHUB_CLIENT_ID, HTTPError, PATHS, cacheModels, cacheVSCodeVersion, ensurePaths, getCopilotUsage, githubHeaders, mergeConfigWithDefaults, sleep, standardHeaders, state } from "./config-CIuwVH6R.js";
3
3
  import { defineCommand, runMain } from "citty";
4
4
  import consola from "consola";
5
5
  import fs from "node:fs/promises";
@@ -430,7 +430,7 @@ async function runServer(options) {
430
430
  }
431
431
  }
432
432
  consola.box(`🌐 Usage Viewer: https://ericc-ch.github.io/copilot-api?endpoint=${serverUrl}/usage`);
433
- const { server } = await import("./server-DbxsVRo3.js");
433
+ const { server } = await import("./server-DbLZqBhL.js");
434
434
  serve({
435
435
  fetch: server.fetch,
436
436
  port: options.port,
@@ -1,4 +1,4 @@
1
- import { HTTPError, PATHS, cacheModels, copilotBaseUrl, copilotHeaders, forwardError, getConfig, getCopilotUsage, getExtraPromptForModel, getReasoningEffortForModel, getSmallModel, isNullish, shouldCompactUseSmallModel, sleep, state } from "./config-Cx3UwWih.js";
1
+ import { HTTPError, PATHS, cacheModels, copilotBaseUrl, copilotHeaders, forwardError, getConfig, getCopilotUsage, getExtraPromptForModel, getReasoningEffortForModel, getSmallModel, isNullish, shouldCompactUseSmallModel, sleep, state } from "./config-CIuwVH6R.js";
2
2
  import consola from "consola";
3
3
  import path from "node:path";
4
4
  import fs from "node:fs";
@@ -417,7 +417,7 @@ const getTokenCount = async (payload, model) => {
417
417
 
418
418
  //#endregion
419
419
  //#region src/services/copilot/create-chat-completions.ts
420
- const createChatCompletions = async (payload) => {
420
+ const createChatCompletions = async (payload, options) => {
421
421
  if (!state.copilotToken) throw new Error("Copilot token not found");
422
422
  const enableVision = payload.messages.some((x) => typeof x.content !== "string" && x.content?.some((x$1) => x$1.type === "image_url"));
423
423
  let isAgentCall = false;
@@ -427,7 +427,7 @@ const createChatCompletions = async (payload) => {
427
427
  }
428
428
  const headers = {
429
429
  ...copilotHeaders(state, enableVision),
430
- "X-Initiator": isAgentCall ? "agent" : "user"
430
+ "X-Initiator": options?.initiator ?? (isAgentCall ? "agent" : "user")
431
431
  };
432
432
  const response = await fetch(`${copilotBaseUrl(state)}/chat/completions`, {
433
433
  method: "POST",
@@ -1636,15 +1636,16 @@ const containsVisionContent = (value) => {
1636
1636
 
1637
1637
  //#endregion
1638
1638
  //#region src/services/copilot/create-messages.ts
1639
- const createMessages = async (payload, anthropicBetaHeader) => {
1639
+ const createMessages = async (payload, anthropicBetaHeader, options) => {
1640
1640
  if (!state.copilotToken) throw new Error("Copilot token not found");
1641
1641
  const enableVision = payload.messages.some((message) => Array.isArray(message.content) && message.content.some((block) => block.type === "image"));
1642
1642
  let isInitiateRequest = false;
1643
1643
  const lastMessage = payload.messages.at(-1);
1644
1644
  if (lastMessage?.role === "user") isInitiateRequest = Array.isArray(lastMessage.content) ? lastMessage.content.some((block) => block.type !== "tool_result") : true;
1645
+ const initiator = options?.initiator ?? (isInitiateRequest ? "user" : "agent");
1645
1646
  const headers = {
1646
1647
  ...copilotHeaders(state, enableVision),
1647
- "X-Initiator": isInitiateRequest ? "user" : "agent"
1648
+ "X-Initiator": initiator
1648
1649
  };
1649
1650
  if (anthropicBetaHeader) {
1650
1651
  const filteredBeta = anthropicBetaHeader.split(",").map((item) => item.trim()).filter((item) => item !== "claude-code-20250219").join(",");
@@ -1913,6 +1914,48 @@ function closeThinkingBlockIfOpen(state$1, events$1) {
1913
1914
  }
1914
1915
  }
1915
1916
 
1917
+ //#endregion
1918
+ //#region src/routes/messages/subagent-marker.ts
1919
+ const subagentMarkerPrefix = "__SUBAGENT_MARKER__";
1920
+ const parseSubagentMarkerFromFirstUser = (payload) => {
1921
+ const firstUserMessage = payload.messages.find((msg) => msg.role === "user");
1922
+ if (!firstUserMessage || !Array.isArray(firstUserMessage.content)) return null;
1923
+ for (const block of firstUserMessage.content) {
1924
+ if (block.type !== "text") continue;
1925
+ const marker = parseSubagentMarkerFromSystemReminder(block.text);
1926
+ if (marker) return marker;
1927
+ }
1928
+ return null;
1929
+ };
1930
+ const parseSubagentMarkerFromSystemReminder = (text) => {
1931
+ const startTag = "<system-reminder>";
1932
+ const endTag = "</system-reminder>";
1933
+ let searchFrom = 0;
1934
+ while (true) {
1935
+ const reminderStart = text.indexOf(startTag, searchFrom);
1936
+ if (reminderStart === -1) break;
1937
+ const contentStart = reminderStart + 17;
1938
+ const reminderEnd = text.indexOf(endTag, contentStart);
1939
+ if (reminderEnd === -1) break;
1940
+ const reminderContent = text.slice(contentStart, reminderEnd);
1941
+ const markerIndex = reminderContent.indexOf(subagentMarkerPrefix);
1942
+ if (markerIndex === -1) {
1943
+ searchFrom = reminderEnd + 18;
1944
+ continue;
1945
+ }
1946
+ const markerJson = reminderContent.slice(markerIndex + 19).trim();
1947
+ try {
1948
+ const parsed = JSON.parse(markerJson);
1949
+ if (!parsed.session_id || !parsed.agent_id || !parsed.agent_type) continue;
1950
+ return parsed;
1951
+ } catch {
1952
+ searchFrom = reminderEnd + 18;
1953
+ continue;
1954
+ }
1955
+ }
1956
+ return null;
1957
+ };
1958
+
1916
1959
  //#endregion
1917
1960
  //#region src/routes/messages/handler.ts
1918
1961
  const logger$2 = createHandlerLogger("messages-handler");
@@ -1921,6 +1964,9 @@ async function handleCompletion(c) {
1921
1964
  await checkRateLimit(state);
1922
1965
  const anthropicPayload = await c.req.json();
1923
1966
  logger$2.debug("Anthropic request payload:", JSON.stringify(anthropicPayload));
1967
+ const subagentMarker = parseSubagentMarkerFromFirstUser(anthropicPayload);
1968
+ const initiatorOverride = subagentMarker ? "agent" : void 0;
1969
+ if (subagentMarker) logger$2.debug("Detected Subagent marker:", JSON.stringify(subagentMarker));
1924
1970
  const isCompact = isCompactRequest(anthropicPayload);
1925
1971
  const anthropicBeta = c.req.header("anthropic-beta");
1926
1972
  logger$2.debug("Anthropic Beta header:", anthropicBeta);
@@ -1934,17 +1980,18 @@ async function handleCompletion(c) {
1934
1980
  const selectedModel = state.models?.data.find((m) => m.id === anthropicPayload.model);
1935
1981
  if (shouldUseMessagesApi(selectedModel)) return await handleWithMessagesApi(c, anthropicPayload, {
1936
1982
  anthropicBetaHeader: anthropicBeta,
1983
+ initiatorOverride,
1937
1984
  selectedModel
1938
1985
  });
1939
- if (shouldUseResponsesApi(selectedModel)) return await handleWithResponsesApi(c, anthropicPayload);
1940
- return await handleWithChatCompletions(c, anthropicPayload);
1986
+ if (shouldUseResponsesApi(selectedModel)) return await handleWithResponsesApi(c, anthropicPayload, initiatorOverride);
1987
+ return await handleWithChatCompletions(c, anthropicPayload, initiatorOverride);
1941
1988
  }
1942
1989
  const RESPONSES_ENDPOINT$1 = "/responses";
1943
1990
  const MESSAGES_ENDPOINT = "/v1/messages";
1944
- const handleWithChatCompletions = async (c, anthropicPayload) => {
1991
+ const handleWithChatCompletions = async (c, anthropicPayload, initiatorOverride) => {
1945
1992
  const openAIPayload = translateToOpenAI(anthropicPayload);
1946
1993
  logger$2.debug("Translated OpenAI request payload:", JSON.stringify(openAIPayload));
1947
- const response = await createChatCompletions(openAIPayload);
1994
+ const response = await createChatCompletions(openAIPayload, { initiator: initiatorOverride });
1948
1995
  if (isNonStreaming(response)) {
1949
1996
  logger$2.debug("Non-streaming response from Copilot:", JSON.stringify(response));
1950
1997
  const anthropicResponse = translateToAnthropic(response);
@@ -1976,13 +2023,13 @@ const handleWithChatCompletions = async (c, anthropicPayload) => {
1976
2023
  }
1977
2024
  });
1978
2025
  };
1979
- const handleWithResponsesApi = async (c, anthropicPayload) => {
2026
+ const handleWithResponsesApi = async (c, anthropicPayload, initiatorOverride) => {
1980
2027
  const responsesPayload = translateAnthropicMessagesToResponsesPayload(anthropicPayload);
1981
2028
  logger$2.debug("Translated Responses payload:", JSON.stringify(responsesPayload));
1982
2029
  const { vision, initiator } = getResponsesRequestOptions(responsesPayload);
1983
2030
  const response = await createResponses(responsesPayload, {
1984
2031
  vision,
1985
- initiator
2032
+ initiator: initiatorOverride ?? initiator
1986
2033
  });
1987
2034
  if (responsesPayload.stream && isAsyncIterable$1(response)) {
1988
2035
  logger$2.debug("Streaming response from Copilot (Responses API)");
@@ -2029,7 +2076,7 @@ const handleWithResponsesApi = async (c, anthropicPayload) => {
2029
2076
  return c.json(anthropicResponse);
2030
2077
  };
2031
2078
  const handleWithMessagesApi = async (c, anthropicPayload, options) => {
2032
- const { anthropicBetaHeader, selectedModel } = options ?? {};
2079
+ const { anthropicBetaHeader, initiatorOverride, selectedModel } = options ?? {};
2033
2080
  for (const msg of anthropicPayload.messages) if (msg.role === "assistant" && Array.isArray(msg.content)) msg.content = msg.content.filter((block) => {
2034
2081
  if (block.type !== "thinking") return true;
2035
2082
  return block.thinking && block.thinking !== "Thinking..." && block.signature && !block.signature.includes("@");
@@ -2039,7 +2086,7 @@ const handleWithMessagesApi = async (c, anthropicPayload, options) => {
2039
2086
  anthropicPayload.output_config = { effort: getAnthropicEffortForModel(anthropicPayload.model) };
2040
2087
  }
2041
2088
  logger$2.debug("Translated Messages payload:", JSON.stringify(anthropicPayload));
2042
- const response = await createMessages(anthropicPayload, anthropicBetaHeader);
2089
+ const response = await createMessages(anthropicPayload, anthropicBetaHeader, { initiator: initiatorOverride });
2043
2090
  if (isAsyncIterable$1(response)) {
2044
2091
  logger$2.debug("Streaming response from Copilot (Messages API)");
2045
2092
  return streamSSE(c, async (stream) => {
@@ -2334,4 +2381,4 @@ server.route("/v1/messages", messageRoutes);
2334
2381
 
2335
2382
  //#endregion
2336
2383
  export { server };
2337
- //# sourceMappingURL=server-DbxsVRo3.js.map
2384
+ //# sourceMappingURL=server-DbLZqBhL.js.map