@openclaw/copilot 2026.6.11 → 2026.7.1-beta.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.
package/README.md CHANGED
@@ -10,10 +10,11 @@ openclaw plugins install @openclaw/copilot
10
10
 
11
11
  Restart the Gateway after installing or updating the plugin.
12
12
 
13
- The harness claims the canonical subscription `github-copilot` provider and
14
- is opt-in only selection requires explicit `agentRuntime.id: "copilot"`
15
- on a model or provider entry; `auto` never picks it. PI remains the default
16
- embedded runtime.
13
+ The harness claims the canonical subscription `github-copilot` provider plus
14
+ custom BYOK provider entries that the Copilot SDK can represent. Manifest-owned
15
+ native provider ids stay with their owning runtimes. The harness is opt-in only:
16
+ selection requires explicit `agentRuntime.id: "copilot"` on a model or provider
17
+ entry; `auto` never picks it. PI remains the default embedded runtime.
17
18
 
18
19
  See [GitHub Copilot agent runtime](../../docs/plugins/copilot.md) for
19
20
  configuration, the doctor contract, transcript mirroring, compaction, side
@@ -1,5 +1,5 @@
1
- import { n as resolveCopilotAuth } from "./harness-D-3KWchY.js";
2
- import { applyEmbeddedAttemptToolsAllow, awaitAgentEndSideEffects, buildAgentHarnessUserInputAnswers, buildAgentHookContextChannelFields, buildEmbeddedAttemptToolRunContext, clearActiveEmbeddedRun, deliverAgentHarnessUserInputPrompt, detectAndLoadAgentHarnessPromptImages, embeddedAgentLog, extractToolErrorMessage, getPluginToolMeta, isSubagentSessionKey, isToolResultError, resolveAgentHarnessBeforePromptBuildResult, resolveAttemptFsWorkspaceOnly, resolveAttemptSpawnWorkspaceDir, resolveBootstrapContextForRun, resolveCompactionTimeoutMs, resolveEmbeddedAttemptToolConstructionPlan, resolveModelAuthMode, resolveSandboxContext, resolveSessionAgentIds, resolveUserPath, runAgentEndSideEffects, runAgentHarnessAfterCompactionHook, runAgentHarnessAfterToolCallHook, runAgentHarnessBeforeCompactionHook, runAgentHarnessBeforeMessageWriteHook, runAgentHarnessLlmInputHook, runAgentHarnessLlmOutputHook, sanitizeToolResult, setActiveEmbeddedRun } from "openclaw/plugin-sdk/agent-harness-runtime";
1
+ import { a as resolveCopilotAuth, i as createCopilotByokAuth, n as resolveCopilotProvider, r as createCopilotByokProxy } from "./harness-B9xB4LpE.js";
2
+ import { applyEmbeddedAttemptToolsAllow, awaitAgentEndSideEffects, buildAgentHarnessUserInputAnswers, buildAgentHookContextChannelFields, buildEmbeddedAttemptToolRunContext, clearActiveEmbeddedRun, deliverAgentHarnessUserInputPrompt, detectAndLoadAgentHarnessPromptImages, embeddedAgentLog, extractToolErrorMessage, getModelProviderRequestTransport, getPluginToolMeta, isSubagentSessionKey, isToolResultError, resolveAgentHarnessBeforePromptBuildResult, resolveAttemptFsWorkspaceOnly, resolveAttemptSpawnWorkspaceDir, resolveBootstrapContextForRun, resolveCompactionTimeoutMs, resolveEmbeddedAttemptToolConstructionPlan, resolveModelAuthMode, resolveSandboxContext, resolveSessionAgentIds, resolveUserPath, runAgentEndSideEffects, runAgentHarnessAfterCompactionHook, runAgentHarnessAfterToolCallHook, runAgentHarnessBeforeCompactionHook, runAgentHarnessBeforeMessageWriteHook, runAgentHarnessLlmInputHook, runAgentHarnessLlmOutputHook, sanitizeToolResult, setActiveEmbeddedRun } from "openclaw/plugin-sdk/agent-harness-runtime";
3
3
  import { createHash } from "node:crypto";
4
4
  import path from "node:path";
5
5
  import fsp from "node:fs/promises";
@@ -242,6 +242,7 @@ function attachEventBridge(session, options) {
242
242
  let detached = false;
243
243
  const unsubscribeFns = [];
244
244
  registerListener(session, unsubscribeFns, "assistant.message_delta", (event) => {
245
+ if (!isRootSessionEvent(event)) return;
245
246
  const messageId = readString$2(event.data.messageId) ?? "assistant-message";
246
247
  const delta = event.data.deltaContent;
247
248
  if (!delta) return;
@@ -264,6 +265,7 @@ function attachEventBridge(session, options) {
264
265
  deltaChain.catch(() => void 0);
265
266
  });
266
267
  registerListener(session, unsubscribeFns, "assistant.reasoning_delta", (event) => {
268
+ if (!isRootSessionEvent(event)) return;
267
269
  const reasoningId = readString$2(event.data.reasoningId) ?? "assistant-reasoning";
268
270
  const delta = event.data.deltaContent;
269
271
  if (!delta) return;
@@ -274,20 +276,22 @@ function attachEventBridge(session, options) {
274
276
  reasoningById.set(reasoningId, `${reasoningById.get(reasoningId) ?? ""}${delta}`);
275
277
  });
276
278
  registerListener(session, unsubscribeFns, "assistant.message", (event) => {
279
+ if (!isRootSessionEvent(event)) return;
277
280
  lastAssistantEvent = event;
278
281
  const entry = ensureMessageAccumulator(messagesById, messageOrder, event.data.messageId);
279
282
  if (typeof event.data.content === "string" && event.data.content.length >= entry.text.length) entry.text = event.data.content;
280
283
  });
281
284
  registerListener(session, unsubscribeFns, "assistant.usage", (event) => {
285
+ if (!isRootSessionEvent(event)) return;
282
286
  usage = normalizeCopilotUsage(event.data);
283
287
  });
284
288
  registerListener(session, unsubscribeFns, "tool.execution_start", (event) => {
285
- startedCount += 1;
289
+ if (isRootSessionEvent(event)) startedCount += 1;
286
290
  toolNamesByCallId.set(event.data.toolCallId, event.data.toolName);
287
291
  toolMetas.push({ toolName: event.data.toolName });
288
292
  });
289
293
  registerListener(session, unsubscribeFns, "tool.execution_complete", (event) => {
290
- completedCount += 1;
294
+ if (isRootSessionEvent(event)) completedCount += 1;
291
295
  const toolName = toolNamesByCallId.get(event.data.toolCallId);
292
296
  const meta = event.data.success ? event.data.result?.detailedContent ?? event.data.result?.content : event.data.error?.message;
293
297
  if (toolName) toolMetas.push({
@@ -324,6 +328,22 @@ function attachEventBridge(session, options) {
324
328
  }
325
329
  });
326
330
  });
331
+ registerListener(session, unsubscribeFns, "exit_plan_mode.completed", (event) => {
332
+ enqueueAgentEvent({
333
+ stream: "plan",
334
+ data: {
335
+ phase: "update",
336
+ title: "Plan decision",
337
+ source: "copilot-sdk",
338
+ requestId: event.data.requestId,
339
+ ...event.data.approved !== void 0 ? { approved: event.data.approved } : {},
340
+ ...event.data.autoApproveEdits !== void 0 ? { autoApproveEdits: event.data.autoApproveEdits } : {},
341
+ ...event.data.feedback ? { feedback: event.data.feedback } : {},
342
+ ...event.data.selectedAction ? { selectedAction: event.data.selectedAction } : {},
343
+ ...event.agentId ? { agentId: event.agentId } : {}
344
+ }
345
+ });
346
+ });
327
347
  registerListener(session, unsubscribeFns, "subagent.started", (event) => {
328
348
  forwardNativeSubagentEvent(event);
329
349
  });
@@ -520,9 +540,12 @@ function finalizeAssistantTexts(messageOrder, messagesById, event) {
520
540
  function isAssistantMessageEvent(event) {
521
541
  return event?.type === "assistant.message";
522
542
  }
523
- function isRootCompactionEvent(event) {
543
+ function isRootSessionEvent(event) {
524
544
  return event.agentId === void 0;
525
545
  }
546
+ function isRootCompactionEvent(event) {
547
+ return isRootSessionEvent(event);
548
+ }
526
549
  function joinReasoning(order, reasoningById) {
527
550
  return order.map((reasoningId) => reasoningById.get(reasoningId) ?? "").join("");
528
551
  }
@@ -964,7 +987,7 @@ function supportsModelTools(modelProvider) {
964
987
  return SUPPORTED_TOOL_PROVIDERS.has(modelProvider);
965
988
  }
966
989
  async function createCopilotToolBridge(input) {
967
- if (!supportsModelTools(input.modelProvider)) return {
990
+ if (!input.allowModelTools && !supportsModelTools(input.modelProvider)) return {
968
991
  sdkTools: [],
969
992
  sourceTools: []
970
993
  };
@@ -1671,7 +1694,6 @@ function readResolvedWorkspacePath(value) {
1671
1694
  }
1672
1695
  //#endregion
1673
1696
  //#region extensions/copilot/src/attempt.ts
1674
- const SUPPORTED_PROVIDERS = new Set(["github-copilot"]);
1675
1697
  const BACKGROUND_COMPACTION_CANCEL_TIMEOUT_MS = 5e3;
1676
1698
  const COPILOT_ASK_USER_AVAILABLE_TOOLS = ["builtin:ask_user"];
1677
1699
  async function runCopilotAgentEndHook(params, hookParams) {
@@ -1735,6 +1757,7 @@ function deferBackgroundCompactionCleanup(params) {
1735
1757
  await params.session.disconnect();
1736
1758
  } catch {}
1737
1759
  params.cleanupToolBridge?.();
1760
+ await params.cleanupByokProxy?.();
1738
1761
  if (outcome !== "completed" && params.sdkSessionId) try {
1739
1762
  await params.handle.client.deleteSession(params.sdkSessionId);
1740
1763
  } catch {}
@@ -1812,13 +1835,21 @@ async function runCopilotAttempt(params, deps) {
1812
1835
  sdkSessionId: void 0,
1813
1836
  sessionIdUsed: input.sessionId
1814
1837
  }));
1815
- if (!SUPPORTED_PROVIDERS.has(modelRef.provider)) return finishAttempt(createResult(input, {
1816
- messagesSnapshot: messages,
1817
- now,
1818
- promptError: createPromptError("model_not_supported", `[copilot-attempt] provider ${modelRef.provider} is not supported at MVP (subscription Copilot models only; BYOK arrives via byok-mapping-skeleton)`),
1819
- sdkSessionId: void 0,
1820
- sessionIdUsed: input.sessionId
1821
- }));
1838
+ try {
1839
+ resolveCopilotProvider({
1840
+ model: modelRef,
1841
+ resolvedApiKey: readString(params.resolvedApiKey),
1842
+ authProfileId: readString(params.authProfileId)
1843
+ });
1844
+ } catch (error) {
1845
+ return finishAttempt(createResult(input, {
1846
+ messagesSnapshot: messages,
1847
+ now,
1848
+ promptError: createPromptError("model_not_supported", toError(error).message, error),
1849
+ sdkSessionId: void 0,
1850
+ sessionIdUsed: input.sessionId
1851
+ }));
1852
+ }
1822
1853
  let abortRequested = false;
1823
1854
  let aborted = false;
1824
1855
  let externalAbort = false;
@@ -1913,11 +1944,26 @@ async function runCopilotAttempt(params, deps) {
1913
1944
  resolvedWorkspace: resolvedWorkspaceForSandbox
1914
1945
  }) : void 0;
1915
1946
  const poolAcquire = resolvePoolAcquire(input);
1947
+ let byokProxy;
1948
+ try {
1949
+ byokProxy = await createCopilotByokProxy(poolAcquire.provider);
1950
+ } catch (error) {
1951
+ return finishAttempt(createResult(input, {
1952
+ messagesSnapshot: messages,
1953
+ now,
1954
+ promptError: createPromptError("model_not_supported", toError(error).message, error),
1955
+ sdkSessionId: void 0,
1956
+ sessionIdUsed: input.sessionId
1957
+ }));
1958
+ }
1959
+ const cleanupByokProxy = byokProxy?.close;
1960
+ const sessionProvider = byokProxy?.provider ?? poolAcquire.provider;
1916
1961
  const sessionRef = { current: void 0 };
1917
1962
  try {
1918
1963
  let sdkTools;
1919
1964
  try {
1920
1965
  const toolBridge = await createToolBridge({
1966
+ allowModelTools: poolAcquire.provider.mode === "byok",
1921
1967
  modelProvider: modelRef.provider,
1922
1968
  modelId: modelRef.id,
1923
1969
  agentId: readString(params.agentId) ?? "copilot",
@@ -1974,6 +2020,7 @@ async function runCopilotAttempt(params, deps) {
1974
2020
  developerInstructions: originalDeveloperInstructions,
1975
2021
  messages,
1976
2022
  ctx: hookContext,
2023
+ bootstrapContextRunKind: input.bootstrapContextRunKind,
1977
2024
  ..."beforeAgentStartResult" in input ? { beforeAgentStartResult: input.beforeAgentStartResult } : {}
1978
2025
  });
1979
2026
  const attemptInput = promptBuild.prompt === input.prompt ? input : {
@@ -2003,7 +2050,8 @@ async function runCopilotAttempt(params, deps) {
2003
2050
  signal: params.abortSignal
2004
2051
  });
2005
2052
  userInputBridgeRef = userInputBridge;
2006
- const sessionConfig = createSessionConfig(attemptInput, modelRef.id, sdkTools, poolAcquire.auth, promptBuild.developerInstructions || void 0, effectiveWorkspaceDir, effectiveCwd, userInputBridge.onUserInputRequest, hasNativePromptHook ? { onUserPromptSubmitted: ({ additionalContext, prompt }) => emitLlmInput(prompt, additionalContext) } : void 0);
2053
+ const sessionConfig = createSessionConfig(attemptInput, modelRef.id, sdkTools, poolAcquire.auth, sessionProvider, promptBuild.developerInstructions || void 0, effectiveWorkspaceDir, effectiveCwd, userInputBridge.onUserInputRequest, hasNativePromptHook ? { onUserPromptSubmitted: ({ additionalContext, prompt }) => emitLlmInput(prompt, additionalContext) } : void 0);
2054
+ const compactionSessionConfig = byokProxy ? createSessionConfig(attemptInput, modelRef.id, sdkTools, poolAcquire.auth, poolAcquire.provider, promptBuild.developerInstructions || void 0, effectiveWorkspaceDir, effectiveCwd, userInputBridge.onUserInputRequest, hasNativePromptHook ? { onUserPromptSubmitted: ({ additionalContext, prompt }) => emitLlmInput(prompt, additionalContext) } : void 0) : sessionConfig;
2007
2055
  const replayDecision = decideReplayAction({
2008
2056
  sdkSessionId: input.initialReplayState?.sdkSessionId,
2009
2057
  replayInvalid: input.initialReplayState?.replayInvalid
@@ -2026,6 +2074,7 @@ async function runCopilotAttempt(params, deps) {
2026
2074
  sessionIdUsed = sdkSessionId ?? input.sessionId;
2027
2075
  if (sdkSessionId && deps.onSessionEstablished) try {
2028
2076
  deps.onSessionEstablished({
2077
+ compactionSessionConfig,
2029
2078
  sdkSessionId,
2030
2079
  pooledClient: handle,
2031
2080
  sessionConfig
@@ -2078,6 +2127,7 @@ async function runCopilotAttempt(params, deps) {
2078
2127
  const messageOptions = await createMessageOptions(attemptInput, {
2079
2128
  effectiveCwd,
2080
2129
  effectiveWorkspaceDir,
2130
+ provider: poolAcquire.provider,
2081
2131
  sandbox,
2082
2132
  workspaceOnly: effectiveFsWorkspaceOnly
2083
2133
  });
@@ -2121,6 +2171,7 @@ async function runCopilotAttempt(params, deps) {
2121
2171
  awaitSessionIdle: !bridge.hasObservedSessionIdle(),
2122
2172
  bridge,
2123
2173
  cleanupToolBridge,
2174
+ cleanupByokProxy,
2124
2175
  finalizeNativeSubagents: () => nativeSubagentTaskMirror?.finalizeActiveRuns(),
2125
2176
  handle,
2126
2177
  pool: deps.pool,
@@ -2144,6 +2195,7 @@ async function runCopilotAttempt(params, deps) {
2144
2195
  await bridge?.awaitAgentEventChain();
2145
2196
  nativeSubagentTaskMirror?.finalizeActiveRuns();
2146
2197
  cleanupToolBridge?.();
2198
+ await cleanupByokProxy?.();
2147
2199
  bridge?.detach();
2148
2200
  params.abortSignal?.removeEventListener("abort", onAbort);
2149
2201
  if (session) try {
@@ -2299,7 +2351,7 @@ function createPromptError(code, message, cause) {
2299
2351
  if (cause !== void 0) error.cause = cause;
2300
2352
  return error;
2301
2353
  }
2302
- function createSessionConfig(params, sdkModelId, sdkTools, resolvedAuth, systemMessageContent, effectiveWorkspaceDir, effectiveCwd, onUserInputRequest, hooksBridgeOptions) {
2354
+ function createSessionConfig(params, sdkModelId, sdkTools, resolvedAuth, resolvedProvider, systemMessageContent, effectiveWorkspaceDir, effectiveCwd, onUserInputRequest, hooksBridgeOptions) {
2303
2355
  const permissionPolicy = params.permissionPolicy ?? rejectAllPolicy;
2304
2356
  const hooks = createHooksBridge(params.hooksConfig, hooksBridgeOptions);
2305
2357
  const infiniteSessions = createInfiniteSessionConfig(params.infiniteSessionConfig);
@@ -2307,6 +2359,7 @@ function createSessionConfig(params, sdkModelId, sdkTools, resolvedAuth, systemM
2307
2359
  model: sdkModelId,
2308
2360
  onPermissionRequest: createPermissionBridge(permissionPolicy),
2309
2361
  onUserInputRequest,
2362
+ ...resolvedProvider.provider ? { provider: resolvedProvider.provider } : {},
2310
2363
  ...hooks ? { hooks } : {},
2311
2364
  ...typeof params.enableSessionTelemetry === "boolean" ? { enableSessionTelemetry: params.enableSessionTelemetry } : {},
2312
2365
  ...infiniteSessions ? { infiniteSessions } : {},
@@ -2327,10 +2380,16 @@ function buildCopilotAvailableTools(sdkTools) {
2327
2380
  }
2328
2381
  async function createMessageOptions(params, context) {
2329
2382
  const attachments = createPromptImageAttachments(await resolvePromptImages(params, context));
2330
- return attachments.length > 0 ? {
2383
+ const requestHeaders = resolveProviderRequestHeaders(context.provider);
2384
+ return {
2331
2385
  prompt: params.prompt,
2332
- attachments
2333
- } : { prompt: params.prompt };
2386
+ ...attachments.length > 0 ? { attachments } : {},
2387
+ ...requestHeaders ? { requestHeaders } : {}
2388
+ };
2389
+ }
2390
+ function resolveProviderRequestHeaders(provider) {
2391
+ const headers = provider.provider?.headers;
2392
+ return headers && Object.keys(headers).length > 0 ? { ...headers } : void 0;
2334
2393
  }
2335
2394
  function createPromptImageAttachments(images) {
2336
2395
  return images.flatMap((image, index) => {
@@ -2373,7 +2432,7 @@ function createSystemMessageContent(params, workspaceBootstrapInstructions) {
2373
2432
  if (bootstrap) sections.push(bootstrap);
2374
2433
  const extraSystemPrompt = readString(params.extraSystemPrompt)?.trim();
2375
2434
  if (extraSystemPrompt && !isRawCopilotModelRun(params)) {
2376
- const contextHeader = params.promptMode === "minimal" ? "## Subagent Context" : "## Group Chat Context";
2435
+ const contextHeader = params.promptMode === "minimal" ? "## Subagent Context" : "## Conversation Context";
2377
2436
  sections.push(`${contextHeader}\n${extraSystemPrompt}`);
2378
2437
  }
2379
2438
  return sections.length > 0 ? sections.join("\n\n") : void 0;
@@ -2416,19 +2475,47 @@ function readResolvedAttemptPath(value) {
2416
2475
  return resolveUserPath(raw);
2417
2476
  }
2418
2477
  function resolveModelRef(params) {
2419
- const rawModel = params.model;
2420
- if (rawModel && typeof rawModel === "object") return {
2421
- api: readString(rawModel.api),
2422
- id: readString(rawModel.id) ?? readString(params.modelId) ?? "unknown-model",
2423
- provider: readString(rawModel.provider) ?? readString(params.provider) ?? "unknown-provider"
2424
- };
2478
+ const rawModel = params.runtimeModel ?? params.model;
2479
+ if (rawModel && typeof rawModel === "object") {
2480
+ const model = rawModel;
2481
+ const requestTransport = getModelProviderRequestTransport(rawModel);
2482
+ const rawRequest = model.request;
2483
+ return {
2484
+ api: readString(model.api),
2485
+ id: readString(model.id) ?? readString(params.modelId) ?? "unknown-model",
2486
+ provider: readString(model.provider) ?? readString(params.provider) ?? "unknown-provider",
2487
+ baseUrl: readString(model.baseUrl),
2488
+ azureApiVersion: readString(model.azureApiVersion ?? model.params?.azureApiVersion),
2489
+ headers: model.headers,
2490
+ authHeader: model.authHeader,
2491
+ requestAuthMode: readString(requestTransport?.auth?.mode ?? rawRequest?.auth?.mode),
2492
+ requestProxy: requestTransport?.proxy ?? rawRequest?.proxy,
2493
+ requestTls: requestTransport?.tls ?? rawRequest?.tls,
2494
+ requestAllowPrivateNetwork: requestTransport?.allowPrivateNetwork ?? rawRequest?.allowPrivateNetwork,
2495
+ contextTokens: model.contextTokens,
2496
+ contextWindow: model.contextWindow,
2497
+ maxTokens: model.maxTokens
2498
+ };
2499
+ }
2425
2500
  return {
2426
2501
  id: readString(typeof rawModel === "string" ? rawModel : void 0) ?? readString(params.modelId) ?? "unknown-model",
2427
2502
  provider: readString(params.provider) ?? "unknown-provider"
2428
2503
  };
2429
2504
  }
2430
2505
  function resolvePoolAcquire(params) {
2431
- const resolved = resolveCopilotAuth({
2506
+ const provider = resolveCopilotProvider({
2507
+ model: resolveModelRef(params),
2508
+ resolvedApiKey: readString(params.resolvedApiKey),
2509
+ authProfileId: readString(params.authProfileId)
2510
+ });
2511
+ const auth = provider.mode === "byok" ? createCopilotByokAuth({
2512
+ agentId: readString(params.agentId),
2513
+ agentDir: readString(params.agentDir),
2514
+ workspaceDir: readString(params.workspaceDir),
2515
+ copilotHome: readString(params.copilotHome),
2516
+ authProfileId: provider.authProfileId,
2517
+ authProfileVersion: provider.authProfileVersion
2518
+ }) : resolveCopilotAuth({
2432
2519
  agentId: readString(params.agentId),
2433
2520
  agentDir: readString(params.agentDir),
2434
2521
  workspaceDir: readString(params.workspaceDir),
@@ -2440,20 +2527,21 @@ function resolvePoolAcquire(params) {
2440
2527
  });
2441
2528
  return {
2442
2529
  key: {
2443
- agentId: resolved.agentId,
2444
- authMode: resolved.authMode,
2445
- ...resolved.authMode === "gitHubToken" ? {
2446
- authProfileId: resolved.authProfileId,
2447
- authProfileVersion: resolved.authProfileVersion
2530
+ agentId: auth.agentId,
2531
+ authMode: auth.authMode,
2532
+ ...auth.authMode === "gitHubToken" || auth.authMode === "byok" ? {
2533
+ authProfileId: auth.authProfileId,
2534
+ authProfileVersion: auth.authProfileVersion
2448
2535
  } : {},
2449
- copilotHome: resolved.copilotHome
2536
+ copilotHome: auth.copilotHome
2450
2537
  },
2451
2538
  options: {
2452
- copilotHome: resolved.copilotHome,
2453
- gitHubToken: resolved.authMode === "gitHubToken" ? resolved.gitHubToken : void 0,
2454
- useLoggedInUser: resolved.authMode === "useLoggedInUser"
2539
+ copilotHome: auth.copilotHome,
2540
+ ...auth.authMode === "gitHubToken" && auth.gitHubToken ? { gitHubToken: auth.gitHubToken } : {},
2541
+ useLoggedInUser: auth.authMode === "useLoggedInUser"
2455
2542
  },
2456
- auth: resolved
2543
+ auth,
2544
+ provider
2457
2545
  };
2458
2546
  }
2459
2547
  function toError(error) {
@@ -1,7 +1,12 @@
1
- import { buildAgentHookContextChannelFields, compactWithSafetyTimeout, resolveCompactionTimeoutMs, runAgentHarnessAfterCompactionHook, runAgentHarnessBeforeCompactionHook } from "openclaw/plugin-sdk/agent-harness-runtime";
2
- import { createHash } from "node:crypto";
1
+ import { buildAgentHookContextChannelFields, compactWithSafetyTimeout, getModelProviderRequestTransport, resolveCompactionTimeoutMs, runAgentHarnessAfterCompactionHook, runAgentHarnessBeforeCompactionHook } from "openclaw/plugin-sdk/agent-harness-runtime";
2
+ import { createHash, randomBytes } from "node:crypto";
3
3
  import { homedir } from "node:os";
4
4
  import { join, resolve } from "node:path";
5
+ import { createServer } from "node:http";
6
+ import { Readable } from "node:stream";
7
+ import { finished } from "node:stream/promises";
8
+ import { fetchWithSsrFGuard, isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
9
+ import { isNonSecretApiKeyMarker } from "openclaw/plugin-sdk/provider-auth";
5
10
  //#region extensions/copilot/src/auth-bridge.ts
6
11
  /**
7
12
  * Pure functional auth resolver for the copilot agent runtime.
@@ -48,6 +53,22 @@ import { join, resolve } from "node:path";
48
53
  */
49
54
  const COPILOT_TOKEN_PROFILE_ERROR = "[copilot-attempt] gitHubToken auth requires profileId+profileVersion (pool keying safety; per Q5/Q1 decisions)";
50
55
  const COPILOT_DEFAULT_AGENT_ID = "copilot";
56
+ function createCopilotByokAuth(input) {
57
+ return {
58
+ ...resolveCopilotAuth({
59
+ agentId: input.agentId,
60
+ agentDir: input.agentDir,
61
+ workspaceDir: input.workspaceDir,
62
+ copilotHome: input.copilotHome,
63
+ env: input.env,
64
+ homeDir: input.homeDir,
65
+ auth: { useLoggedInUser: true }
66
+ }),
67
+ authMode: "byok",
68
+ authProfileId: input.authProfileId?.trim() || "byok:resolved",
69
+ authProfileVersion: input.authProfileVersion?.trim() || "byok:unfingerprinted"
70
+ };
71
+ }
51
72
  /**
52
73
  * Resolve copilot auth + copilotHome.
53
74
  *
@@ -63,16 +84,16 @@ function resolveCopilotAuth(input) {
63
84
  const homeDir = input.homeDir ?? homedir;
64
85
  const agentId = sanitizeAgentId(input.agentId);
65
86
  const copilotHome = resolveCopilotHome({
66
- explicit: readString(input.copilotHome),
67
- agentDir: readString(input.agentDir),
68
- workspaceDir: readString(input.workspaceDir),
87
+ explicit: readString$1(input.copilotHome),
88
+ agentDir: readString$1(input.agentDir),
89
+ workspaceDir: readString$1(input.workspaceDir),
69
90
  agentId,
70
91
  env,
71
92
  homeDir
72
93
  });
73
- const explicitToken = readString(input.auth?.gitHubToken);
74
- const explicitProfileId = readString(input.auth?.profileId) ?? readString(input.authProfileId);
75
- const explicitProfileVersion = readString(input.auth?.profileVersion) ?? readString(input.profileVersion);
94
+ const explicitToken = readString$1(input.auth?.gitHubToken);
95
+ const explicitProfileId = readString$1(input.auth?.profileId) ?? readString$1(input.authProfileId);
96
+ const explicitProfileVersion = readString$1(input.auth?.profileVersion) ?? readString$1(input.profileVersion);
76
97
  if (input.auth?.useLoggedInUser === true) return {
77
98
  authMode: "useLoggedInUser",
78
99
  copilotHome,
@@ -89,11 +110,11 @@ function resolveCopilotAuth(input) {
89
110
  agentId
90
111
  };
91
112
  }
92
- const contractToken = readString(input.resolvedApiKey);
113
+ const contractToken = readString$1(input.resolvedApiKey);
93
114
  if (contractToken) return {
94
115
  authMode: "gitHubToken",
95
116
  gitHubToken: contractToken,
96
- authProfileId: readString(input.authProfileId) ?? "pi:resolved",
117
+ authProfileId: readString$1(input.authProfileId) ?? "pi:resolved",
97
118
  authProfileVersion: tokenFingerprint(contractToken),
98
119
  copilotHome,
99
120
  agentId
@@ -134,7 +155,7 @@ function sanitizeAgentId(value) {
134
155
  function resolveCopilotHome(args) {
135
156
  if (args.explicit) return resolve(args.explicit);
136
157
  if (args.agentDir) return resolve(join(args.agentDir, "copilot"));
137
- const openClawHome = readString(args.env.OPENCLAW_HOME);
158
+ const openClawHome = readString$1(args.env.OPENCLAW_HOME);
138
159
  return resolve(join(openClawHome ? resolve(openClawHome) : safeHomeDir(args.homeDir), ".openclaw", "agents", args.agentId, "copilot"));
139
160
  }
140
161
  function safeHomeDir(homeDir) {
@@ -148,19 +169,19 @@ function readEnvTokenFallback(env) {
148
169
  const candidates = [
149
170
  {
150
171
  name: "OPENCLAW_GITHUB_TOKEN",
151
- value: readString(env.OPENCLAW_GITHUB_TOKEN)
172
+ value: readString$1(env.OPENCLAW_GITHUB_TOKEN)
152
173
  },
153
174
  {
154
175
  name: "COPILOT_GITHUB_TOKEN",
155
- value: readString(env.COPILOT_GITHUB_TOKEN)
176
+ value: readString$1(env.COPILOT_GITHUB_TOKEN)
156
177
  },
157
178
  {
158
179
  name: "GH_TOKEN",
159
- value: readString(env.GH_TOKEN)
180
+ value: readString$1(env.GH_TOKEN)
160
181
  },
161
182
  {
162
183
  name: "GITHUB_TOKEN",
163
- value: readString(env.GITHUB_TOKEN)
184
+ value: readString$1(env.GITHUB_TOKEN)
164
185
  }
165
186
  ];
166
187
  for (const { name, value } of candidates) if (value) return {
@@ -180,15 +201,382 @@ function readEnvTokenFallback(env) {
180
201
  function tokenFingerprint(token) {
181
202
  return `sha256:${createHash("sha256").update(token).digest("hex").slice(0, 12)}`;
182
203
  }
183
- function readString(value) {
204
+ function readString$1(value) {
184
205
  return typeof value === "string" && value.length > 0 ? value : void 0;
185
206
  }
186
207
  //#endregion
208
+ //#region extensions/copilot/src/byok-proxy.ts
209
+ const LOOPBACK_HOST = "127.0.0.1";
210
+ async function createCopilotByokProxy(resolvedProvider) {
211
+ if (resolvedProvider.mode !== "byok") return;
212
+ const providerConfig = resolvedProvider.provider;
213
+ if (!providerConfig?.baseUrl) throw new Error("[copilot-attempt] BYOK requires a provider baseUrl");
214
+ const targetBaseUrl = new URL(providerConfig.baseUrl);
215
+ const nonce = randomBytes(12).toString("hex");
216
+ const targetPathPrefix = trimTrailingSlash(targetBaseUrl.pathname);
217
+ const proxyPathPrefix = `/${nonce}${targetPathPrefix}`;
218
+ const acceptsAzureSdkPaths = providerConfig.type === "azure";
219
+ const activeFetches = /* @__PURE__ */ new Set();
220
+ const server = createServer((req, res) => {
221
+ handleProxyRequest(req, res, {
222
+ acceptsAzureSdkPaths,
223
+ activeFetches,
224
+ proxyPathPrefix,
225
+ targetBaseUrl,
226
+ targetPathPrefix
227
+ });
228
+ });
229
+ await new Promise((resolve, reject) => {
230
+ server.once("error", reject);
231
+ server.listen(0, LOOPBACK_HOST, () => {
232
+ server.off("error", reject);
233
+ resolve();
234
+ });
235
+ });
236
+ const address = server.address();
237
+ if (!address || typeof address === "string") {
238
+ server.close();
239
+ throw new Error("[copilot-attempt] failed to start BYOK network proxy");
240
+ }
241
+ const proxyBaseUrl = `http://${LOOPBACK_HOST}:${address.port}${proxyPathPrefix}`;
242
+ const sdkBaseUrl = acceptsAzureSdkPaths ? `http://${LOOPBACK_HOST}:${address.port}` : proxyBaseUrl;
243
+ return {
244
+ provider: {
245
+ ...resolvedProvider,
246
+ provider: {
247
+ ...providerConfig,
248
+ baseUrl: sdkBaseUrl
249
+ }
250
+ },
251
+ close: async () => {
252
+ for (const controller of activeFetches) controller.abort();
253
+ await new Promise((resolve) => {
254
+ server.close(() => resolve());
255
+ });
256
+ }
257
+ };
258
+ }
259
+ async function handleProxyRequest(req, res, params) {
260
+ let guarded;
261
+ const upstreamAbort = new AbortController();
262
+ params.activeFetches.add(upstreamAbort);
263
+ const abortUpstream = () => upstreamAbort.abort();
264
+ req.on("aborted", abortUpstream);
265
+ res.on("close", () => {
266
+ if (!res.writableEnded) abortUpstream();
267
+ });
268
+ try {
269
+ const url = resolveTargetUrl(req, params);
270
+ if (!url) {
271
+ res.writeHead(404);
272
+ res.end("Not found");
273
+ return;
274
+ }
275
+ const body = req.method === "GET" || req.method === "HEAD" ? void 0 : await readBody(req);
276
+ guarded = await fetchWithSsrFGuard({
277
+ url: url.toString(),
278
+ init: {
279
+ method: req.method,
280
+ headers: normalizeProxyRequestHeaders(req.headers),
281
+ signal: upstreamAbort.signal,
282
+ ...body ? { body: toFetchBody(body) } : {}
283
+ },
284
+ auditContext: "copilot-byok-provider",
285
+ requireHttps: true
286
+ });
287
+ res.writeHead(guarded.response.status, guarded.response.statusText, normalizeProxyResponseHeaders(guarded.response.headers));
288
+ if (!guarded.response.body) {
289
+ res.end();
290
+ return;
291
+ }
292
+ await finished(Readable.fromWeb(guarded.response.body).pipe(res));
293
+ } catch (error) {
294
+ if (res.destroyed || res.writableEnded) return;
295
+ if (res.headersSent) {
296
+ res.destroy(error instanceof Error ? error : void 0);
297
+ return;
298
+ }
299
+ res.writeHead(502);
300
+ res.end(error instanceof Error ? error.message : "BYOK provider proxy failed");
301
+ } finally {
302
+ req.off("aborted", abortUpstream);
303
+ params.activeFetches.delete(upstreamAbort);
304
+ await guarded?.release().catch(() => void 0);
305
+ }
306
+ }
307
+ function resolveTargetUrl(req, params) {
308
+ const incomingUrl = new URL(req.url ?? "/", `http://${LOOPBACK_HOST}`);
309
+ if (incomingUrl.pathname !== params.proxyPathPrefix && !incomingUrl.pathname.startsWith(`${params.proxyPathPrefix}/`)) return params.acceptsAzureSdkPaths && isAzureSdkProxyPath(incomingUrl.pathname) ? resolveDirectTargetUrl(incomingUrl, params.targetBaseUrl) : void 0;
310
+ const suffix = incomingUrl.pathname.slice(params.proxyPathPrefix.length);
311
+ const targetUrl = new URL(params.targetBaseUrl);
312
+ targetUrl.pathname = `${params.targetPathPrefix}${suffix}` || "/";
313
+ for (const [key, value] of incomingUrl.searchParams) targetUrl.searchParams.append(key, value);
314
+ return targetUrl;
315
+ }
316
+ function resolveDirectTargetUrl(incomingUrl, targetBaseUrl) {
317
+ const targetUrl = new URL(targetBaseUrl);
318
+ targetUrl.pathname = incomingUrl.pathname;
319
+ for (const [key, value] of incomingUrl.searchParams) targetUrl.searchParams.append(key, value);
320
+ return targetUrl;
321
+ }
322
+ function isAzureSdkProxyPath(pathname) {
323
+ return pathname === "/openai" || pathname.startsWith("/openai/");
324
+ }
325
+ async function readBody(req) {
326
+ const chunks = [];
327
+ for await (const chunk of req) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
328
+ return chunks.length > 0 ? Buffer.concat(chunks) : void 0;
329
+ }
330
+ function toFetchBody(body) {
331
+ const copy = new Uint8Array(body.byteLength);
332
+ copy.set(body);
333
+ return copy;
334
+ }
335
+ function normalizeProxyRequestHeaders(headers) {
336
+ const out = {};
337
+ for (const [key, value] of Object.entries(headers)) {
338
+ if (isHopByHopHeader(key) || key.toLowerCase() === "accept-encoding") continue;
339
+ const normalized = normalizeHeaderValue(value);
340
+ if (normalized !== void 0) out[key] = normalized;
341
+ }
342
+ out["accept-encoding"] = "identity";
343
+ return out;
344
+ }
345
+ function normalizeProxyResponseHeaders(headers) {
346
+ const out = {};
347
+ headers.forEach((value, key) => {
348
+ if (!isHopByHopHeader(key) && !isContentEncodingHeader(key)) out[key] = value;
349
+ });
350
+ return out;
351
+ }
352
+ function normalizeHeaderValue(value) {
353
+ if (value === void 0) return;
354
+ return Array.isArray(value) ? value.join(", ") : String(value);
355
+ }
356
+ function isHopByHopHeader(key) {
357
+ switch (key.toLowerCase()) {
358
+ case "connection":
359
+ case "host":
360
+ case "keep-alive":
361
+ case "proxy-authenticate":
362
+ case "proxy-authorization":
363
+ case "te":
364
+ case "trailer":
365
+ case "transfer-encoding":
366
+ case "upgrade": return true;
367
+ default: return false;
368
+ }
369
+ }
370
+ function isContentEncodingHeader(key) {
371
+ switch (key.toLowerCase()) {
372
+ case "content-encoding":
373
+ case "content-length": return true;
374
+ default: return false;
375
+ }
376
+ }
377
+ function trimTrailingSlash(pathname) {
378
+ const trimmed = pathname.replace(/\/+$/, "");
379
+ return trimmed === "" ? "" : trimmed;
380
+ }
381
+ //#endregion
382
+ //#region extensions/copilot/src/provider-bridge.ts
383
+ const COPILOT_BYOK_PROVIDER_ERROR = "[copilot-attempt] BYOK requires an OpenAI-compatible or Anthropic model api and a non-empty baseUrl";
384
+ const COPILOT_BYOK_TRANSPORT_POLICY_ERROR = "[copilot-attempt] BYOK does not support OpenClaw provider request proxy, TLS, or private-network policy overrides";
385
+ const COPILOT_BYOK_ENDPOINT_POLICY_ERROR = "[copilot-attempt] BYOK endpoint is blocked by OpenClaw SSRF policy";
386
+ const CREDENTIAL_QUERY_PARAM_NAMES = new Set([
387
+ "accesstoken",
388
+ "appsecret",
389
+ "auth",
390
+ "authtoken",
391
+ "apikey",
392
+ "authorization",
393
+ "clientsecret",
394
+ "code",
395
+ "credential",
396
+ "hooktoken",
397
+ "idtoken",
398
+ "jwt",
399
+ "key",
400
+ "pass",
401
+ "passwd",
402
+ "password",
403
+ "privatekey",
404
+ "refreshtoken",
405
+ "secret",
406
+ "session",
407
+ "sig",
408
+ "signature",
409
+ "token",
410
+ "xapikey",
411
+ "xaccesstoken",
412
+ "xamzsecuritytoken",
413
+ "xamzsignature",
414
+ "xauthtoken"
415
+ ]);
416
+ const QUERY_PARAM_NAME_SEPARATOR_RE = /[\p{C}\p{Z}\u115F\u1160\u3164\uFFA0+]/gu;
417
+ /**
418
+ * Maps OpenClaw's prepared model facts into the Copilot SDK's session-level
419
+ * provider contract. The SDK owns the wire request; OpenClaw only supplies
420
+ * the already-resolved endpoint, model, headers, and credential.
421
+ */
422
+ function resolveCopilotProvider(params) {
423
+ if (params.model.provider.trim().toLowerCase() === "github-copilot") return { mode: "github-copilot" };
424
+ const baseUrl = readString(params.model.baseUrl);
425
+ if (!baseUrl) throw new Error(COPILOT_BYOK_PROVIDER_ERROR);
426
+ assertByokEndpointAllowed(baseUrl);
427
+ if (hasUnsupportedTransportPolicy(params.model)) throw new Error(COPILOT_BYOK_TRANSPORT_POLICY_ERROR);
428
+ const api = readString(params.model.api)?.toLowerCase() ?? "openai-responses";
429
+ const provider = resolveProviderType(api, baseUrl, params.model.azureApiVersion);
430
+ const resolvedApiKey = resolveProviderCredential(params.resolvedApiKey);
431
+ const headers = resolveProviderHeaders(params.model.headers);
432
+ const requestAuthMode = readString(params.model.requestAuthMode)?.toLowerCase();
433
+ const usePreparedRequestAuth = requestAuthMode !== void 0 && requestAuthMode !== "provider-default";
434
+ return {
435
+ mode: "byok",
436
+ provider: {
437
+ type: provider.type,
438
+ ...provider.wireApi ? { wireApi: provider.wireApi } : {},
439
+ baseUrl: provider.baseUrl,
440
+ modelId: params.model.id,
441
+ wireModel: params.model.id,
442
+ ...resolvedApiKey && !usePreparedRequestAuth ? params.model.authHeader ? { bearerToken: resolvedApiKey } : { apiKey: resolvedApiKey } : {},
443
+ ...headers ? { headers } : {},
444
+ ...provider.azure ? { azure: provider.azure } : {},
445
+ ...params.model.contextTokens ?? params.model.contextWindow ? { maxPromptTokens: params.model.contextTokens ?? params.model.contextWindow } : {},
446
+ ...params.model.maxTokens ? { maxOutputTokens: params.model.maxTokens } : {}
447
+ },
448
+ authProfileId: params.authProfileId?.trim() || `byok:${params.model.provider}`,
449
+ authProfileVersion: tokenFingerprint(stableSerialize({
450
+ api,
451
+ baseUrl: provider.baseUrl,
452
+ azureApiVersion: provider.azure?.apiVersion,
453
+ headers,
454
+ authHeader: params.model.authHeader,
455
+ requestAuthMode: params.model.requestAuthMode,
456
+ apiKey: resolvedApiKey,
457
+ modelId: params.model.id,
458
+ maxPromptTokens: params.model.contextTokens ?? params.model.contextWindow,
459
+ maxOutputTokens: params.model.maxTokens
460
+ }))
461
+ };
462
+ }
463
+ function isCopilotByokUnsupportedProviderError(error) {
464
+ return error instanceof Error && (error.message === "[copilot-attempt] BYOK requires an OpenAI-compatible or Anthropic model api and a non-empty baseUrl" || error.message === "[copilot-attempt] BYOK does not support OpenClaw provider request proxy, TLS, or private-network policy overrides" || error.message === "[copilot-attempt] BYOK endpoint is blocked by OpenClaw SSRF policy");
465
+ }
466
+ function supportsCopilotByokProviderShape(model) {
467
+ if (!readString(model.baseUrl) || hasUnsupportedTransportPolicy(model)) return false;
468
+ try {
469
+ resolveProviderType(readString(model.api)?.toLowerCase() ?? "openai-responses", readString(model.baseUrl), void 0);
470
+ assertByokEndpointHostAllowed(readString(model.baseUrl));
471
+ return true;
472
+ } catch {
473
+ return false;
474
+ }
475
+ }
476
+ function hasUnsupportedTransportPolicy(model) {
477
+ return model.requestProxy !== void 0 || model.requestTls !== void 0 || model.requestAllowPrivateNetwork !== void 0;
478
+ }
479
+ function assertByokEndpointHostAllowed(baseUrl) {
480
+ let url;
481
+ try {
482
+ url = new URL(baseUrl);
483
+ } catch {
484
+ throw new Error(COPILOT_BYOK_PROVIDER_ERROR);
485
+ }
486
+ if (url.protocol !== "https:") throw new Error(COPILOT_BYOK_ENDPOINT_POLICY_ERROR);
487
+ if (url.username || url.password) throw new Error(COPILOT_BYOK_ENDPOINT_POLICY_ERROR);
488
+ for (const key of url.searchParams.keys()) if (CREDENTIAL_QUERY_PARAM_NAMES.has(normalizeCredentialQueryParamName(key))) throw new Error(COPILOT_BYOK_ENDPOINT_POLICY_ERROR);
489
+ if (isBlockedHostnameOrIp(url.hostname.toLowerCase().replace(/\.+$/, ""))) throw new Error(COPILOT_BYOK_ENDPOINT_POLICY_ERROR);
490
+ }
491
+ function normalizeCredentialQueryParamName(name) {
492
+ const stripped = name.replace(QUERY_PARAM_NAME_SEPARATOR_RE, "");
493
+ try {
494
+ return decodeURIComponent(stripped).replace(QUERY_PARAM_NAME_SEPARATOR_RE, "").toLowerCase().replace(/[-_]/g, "");
495
+ } catch {
496
+ return stripped.toLowerCase().replace(/[-_]/g, "");
497
+ }
498
+ }
499
+ function assertByokEndpointAllowed(baseUrl) {
500
+ assertByokEndpointHostAllowed(baseUrl);
501
+ }
502
+ function resolveProviderType(api, baseUrl, azureApiVersion) {
503
+ switch (api) {
504
+ case "anthropic-messages": return {
505
+ type: "anthropic",
506
+ baseUrl
507
+ };
508
+ case "azure-openai-responses": return resolveAzureProviderType(baseUrl, azureApiVersion);
509
+ case "openai-responses": return {
510
+ type: "openai",
511
+ wireApi: "responses",
512
+ baseUrl
513
+ };
514
+ case "openai-completions":
515
+ case "ollama": return {
516
+ type: "openai",
517
+ wireApi: "completions",
518
+ baseUrl
519
+ };
520
+ default: throw new Error(COPILOT_BYOK_PROVIDER_ERROR);
521
+ }
522
+ }
523
+ function resolveAzureProviderType(baseUrl, apiVersion) {
524
+ let url;
525
+ try {
526
+ url = new URL(baseUrl);
527
+ } catch {
528
+ throw new Error(COPILOT_BYOK_PROVIDER_ERROR);
529
+ }
530
+ if (isOpenAICompatibleAzureResponsesBaseUrl(url)) return {
531
+ type: "openai",
532
+ wireApi: "responses",
533
+ baseUrl
534
+ };
535
+ if (!isTraditionalAzureOpenAIHost(url.hostname)) throw new Error(COPILOT_BYOK_PROVIDER_ERROR);
536
+ url.pathname = "";
537
+ url.search = "";
538
+ url.hash = "";
539
+ const resolvedApiVersion = readString(apiVersion);
540
+ return {
541
+ type: "azure",
542
+ wireApi: "responses",
543
+ baseUrl: url.toString().replace(/\/+$/, ""),
544
+ ...resolvedApiVersion ? { azure: { apiVersion: resolvedApiVersion } } : {}
545
+ };
546
+ }
547
+ function isTraditionalAzureOpenAIHost(hostname) {
548
+ return hostname.endsWith(".openai.azure.com") || hostname.endsWith(".cognitiveservices.azure.com");
549
+ }
550
+ function isOpenAICompatibleAzureResponsesBaseUrl(url) {
551
+ if (isTraditionalAzureOpenAIHost(url.hostname)) return false;
552
+ const hostname = url.hostname.toLowerCase();
553
+ if (!(hostname.endsWith(".services.ai.azure.com") || hostname.endsWith(".api.cognitive.microsoft.com"))) return false;
554
+ const normalizedPath = url.pathname.replace(/\/+$/, "");
555
+ return normalizedPath === "/openai/v1" || normalizedPath.endsWith("/openai/v1");
556
+ }
557
+ function stableSerialize(value) {
558
+ if (Array.isArray(value)) return `[${value.map(stableSerialize).join(",")}]`;
559
+ if (value && typeof value === "object") return `{${Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => `${JSON.stringify(key)}:${stableSerialize(entry)}`).join(",")}}`;
560
+ return JSON.stringify(value) ?? "null";
561
+ }
562
+ function readString(value) {
563
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
564
+ }
565
+ function resolveProviderCredential(value) {
566
+ const credential = readString(value);
567
+ return credential && !isNonSecretApiKeyMarker(credential) ? credential : void 0;
568
+ }
569
+ function resolveProviderHeaders(headers) {
570
+ if (!headers) return;
571
+ const resolved = Object.fromEntries(Object.entries(headers).filter(([, value]) => typeof value === "string"));
572
+ return Object.keys(resolved).length > 0 ? resolved : void 0;
573
+ }
574
+ //#endregion
187
575
  //#region extensions/copilot/harness.ts
188
576
  const COPILOT_PROVIDER_IDS = new Set(["github-copilot"]);
189
577
  function sessionAuthFields(auth) {
190
- return auth.authMode === "gitHubToken" ? {
191
- authMode: "gitHubToken",
578
+ return auth.authMode === "gitHubToken" || auth.authMode === "byok" ? {
579
+ authMode: auth.authMode,
192
580
  authProfileId: auth.authProfileId,
193
581
  authProfileVersion: auth.authProfileVersion
194
582
  } : { authMode: "useLoggedInUser" };
@@ -196,17 +584,17 @@ function sessionAuthFields(auth) {
196
584
  function sessionAuthMatches(stored, current) {
197
585
  if (stored.authMode !== current.authMode) return false;
198
586
  if (stored.authMode === "useLoggedInUser") return true;
199
- return current.authMode === "gitHubToken" && stored.authProfileId === current.authProfileId && stored.authProfileVersion === current.authProfileVersion;
587
+ return current.authMode === stored.authMode && stored.authProfileId === current.authProfileId && stored.authProfileVersion === current.authProfileVersion;
200
588
  }
201
589
  function normalizeBinding(value) {
202
- if (!value || value.schemaVersion !== 2 || typeof value.sdkSessionId !== "string" || value.sdkSessionId.trim() === "" || typeof value.compatKey !== "string" || value.compatKey.trim() === "" || typeof value.compactKey !== "string" || value.compactKey.trim() === "" || value.authMode !== "gitHubToken" && value.authMode !== "useLoggedInUser" || value.authMode === "gitHubToken" && (typeof value.authProfileId !== "string" || value.authProfileId.trim() === "" || typeof value.authProfileVersion !== "string" || value.authProfileVersion.trim() === "") || typeof value.updatedAt !== "number" || !Number.isFinite(value.updatedAt)) return;
590
+ if (!value || value.schemaVersion !== 2 || typeof value.sdkSessionId !== "string" || value.sdkSessionId.trim() === "" || typeof value.compatKey !== "string" || value.compatKey.trim() === "" || typeof value.compactKey !== "string" || value.compactKey.trim() === "" || value.authMode !== "gitHubToken" && value.authMode !== "byok" && value.authMode !== "useLoggedInUser" || (value.authMode === "gitHubToken" || value.authMode === "byok") && (typeof value.authProfileId !== "string" || value.authProfileId.trim() === "" || typeof value.authProfileVersion !== "string" || value.authProfileVersion.trim() === "") || typeof value.updatedAt !== "number" || !Number.isFinite(value.updatedAt)) return;
203
591
  return {
204
592
  schemaVersion: 2,
205
593
  sdkSessionId: value.sdkSessionId.trim(),
206
594
  compatKey: value.compatKey,
207
595
  compactKey: value.compactKey,
208
596
  authMode: value.authMode,
209
- ...value.authMode === "gitHubToken" ? {
597
+ ...value.authMode === "gitHubToken" || value.authMode === "byok" ? {
210
598
  authProfileId: value.authProfileId,
211
599
  authProfileVersion: value.authProfileVersion
212
600
  } : {},
@@ -290,23 +678,61 @@ function readAgentIdFromSessionKey(sessionKey) {
290
678
  }
291
679
  function computeSessionKey(params, options) {
292
680
  const p = params;
293
- const modelObj = p.model && typeof p.model === "object" ? p.model : { id: typeof p.model === "string" ? p.model : void 0 };
681
+ const modelObj = p.model && typeof p.model === "object" ? p.model : p.runtimeModel && typeof p.runtimeModel === "object" ? p.runtimeModel : { id: typeof p.model === "string" ? p.model : void 0 };
294
682
  const provider = modelObj.provider ?? (typeof p.provider === "string" ? p.provider : "");
295
683
  const modelId = modelObj.id ?? (typeof p.modelId === "string" ? p.modelId : void 0) ?? (typeof p.model === "string" ? p.model : "");
684
+ const requestTransport = p.model && typeof p.model === "object" ? getModelProviderRequestTransport(p.model) : void 0;
685
+ const requestAuthMode = readSessionString(requestTransport?.auth?.mode ?? modelObj.request?.auth?.mode);
686
+ const azureApiVersion = readSessionString(modelObj.azureApiVersion ?? modelObj.params?.azureApiVersion);
296
687
  let authParts;
297
688
  let resolvedAgentId = "";
298
689
  let resolvedCopilotHome = "";
299
690
  try {
300
- const resolved = resolveCopilotAuth({
691
+ const resolved = !options.includeAuth ? resolveCopilotAuth({
301
692
  agentId: typeof p.agentId === "string" ? p.agentId : readAgentIdFromSessionKey(p.sessionKey),
302
693
  agentDir: typeof p.agentDir === "string" ? p.agentDir : void 0,
303
694
  workspaceDir: typeof p.workspaceDir === "string" ? p.workspaceDir : void 0,
304
695
  copilotHome: typeof p.copilotHome === "string" ? p.copilotHome : void 0,
305
- auth: p.auth,
306
- resolvedApiKey: typeof p.resolvedApiKey === "string" ? p.resolvedApiKey : void 0,
307
- authProfileId: typeof p.authProfileId === "string" ? p.authProfileId : void 0,
308
- profileVersion: typeof p.profileVersion === "string" ? p.profileVersion : void 0
309
- });
696
+ auth: { useLoggedInUser: true }
697
+ }) : (() => {
698
+ const modelProvider = resolveCopilotProvider({
699
+ model: {
700
+ api: modelObj.api,
701
+ id: modelId,
702
+ provider,
703
+ baseUrl: modelObj.baseUrl,
704
+ azureApiVersion,
705
+ headers: modelObj.headers,
706
+ authHeader: modelObj.authHeader,
707
+ requestAuthMode,
708
+ requestProxy: requestTransport?.proxy ?? modelObj.request?.proxy,
709
+ requestTls: requestTransport?.tls ?? modelObj.request?.tls,
710
+ requestAllowPrivateNetwork: requestTransport?.allowPrivateNetwork ?? modelObj.request?.allowPrivateNetwork,
711
+ contextTokens: modelObj.contextTokens,
712
+ contextWindow: modelObj.contextWindow,
713
+ maxTokens: modelObj.maxTokens
714
+ },
715
+ resolvedApiKey: typeof p.resolvedApiKey === "string" ? p.resolvedApiKey : void 0,
716
+ authProfileId: typeof p.authProfileId === "string" ? p.authProfileId : void 0
717
+ });
718
+ return modelProvider.mode === "byok" ? createCopilotByokAuth({
719
+ agentId: typeof p.agentId === "string" ? p.agentId : readAgentIdFromSessionKey(p.sessionKey),
720
+ agentDir: typeof p.agentDir === "string" ? p.agentDir : void 0,
721
+ workspaceDir: typeof p.workspaceDir === "string" ? p.workspaceDir : void 0,
722
+ copilotHome: typeof p.copilotHome === "string" ? p.copilotHome : void 0,
723
+ authProfileId: modelProvider.authProfileId,
724
+ authProfileVersion: modelProvider.authProfileVersion
725
+ }) : resolveCopilotAuth({
726
+ agentId: typeof p.agentId === "string" ? p.agentId : readAgentIdFromSessionKey(p.sessionKey),
727
+ agentDir: typeof p.agentDir === "string" ? p.agentDir : void 0,
728
+ workspaceDir: typeof p.workspaceDir === "string" ? p.workspaceDir : void 0,
729
+ copilotHome: typeof p.copilotHome === "string" ? p.copilotHome : void 0,
730
+ auth: p.auth,
731
+ resolvedApiKey: typeof p.resolvedApiKey === "string" ? p.resolvedApiKey : void 0,
732
+ authProfileId: typeof p.authProfileId === "string" ? p.authProfileId : void 0,
733
+ profileVersion: typeof p.profileVersion === "string" ? p.profileVersion : void 0
734
+ });
735
+ })();
310
736
  resolvedAgentId = resolved.agentId;
311
737
  resolvedCopilotHome = resolved.copilotHome;
312
738
  authParts = [
@@ -314,6 +740,7 @@ function computeSessionKey(params, options) {
314
740
  `auth.profileId=${resolved.authProfileId ?? ""}`,
315
741
  `auth.profileVersion=${resolved.authProfileVersion ?? ""}`
316
742
  ];
743
+ if (!options.includeAuth) authParts = [];
317
744
  } catch {
318
745
  authParts = ["auth=unresolvable"];
319
746
  }
@@ -321,6 +748,7 @@ function computeSessionKey(params, options) {
321
748
  `provider=${provider}`,
322
749
  `model=${modelId}`,
323
750
  ...options.includeApi ? [`api=${modelObj.api ?? ""}`] : [],
751
+ ...options.includeApi ? [`baseUrlFingerprint=${fingerprintSessionValue(modelObj.baseUrl)}`] : [],
324
752
  `cwd=${p.cwd ?? p.workspaceDir ?? ""}`,
325
753
  `agentId=${resolvedAgentId}`,
326
754
  `agentDir=${p.agentDir ?? ""}`,
@@ -329,6 +757,12 @@ function computeSessionKey(params, options) {
329
757
  ...options.includeAuth ? authParts : []
330
758
  ].join("|");
331
759
  }
760
+ function readSessionString(value) {
761
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
762
+ }
763
+ function fingerprintSessionValue(value) {
764
+ return typeof value === "string" && value ? tokenFingerprint(value) : "";
765
+ }
332
766
  function computeSessionCompatKey(params) {
333
767
  return computeSessionKey(params, {
334
768
  includeApi: true,
@@ -409,10 +843,29 @@ function createCopilotAgentHarness(options) {
409
843
  reason: "copilot is opt-in only"
410
844
  };
411
845
  const provider = ctx.provider.trim().toLowerCase();
412
- if (!COPILOT_PROVIDER_IDS.has(provider)) return {
846
+ if (!provider) return {
847
+ supported: false,
848
+ reason: "provider is required"
849
+ };
850
+ if (COPILOT_PROVIDER_IDS.has(provider)) return {
851
+ supported: true,
852
+ priority: 100
853
+ };
854
+ const providerOwnerPluginIds = ctx.providerOwnerPluginIds;
855
+ if (ctx.providerOwnerStatus !== "unowned" || !providerOwnerPluginIds || providerOwnerPluginIds.length > 0) return {
413
856
  supported: false,
414
857
  reason: `provider is not one of: ${[...COPILOT_PROVIDER_IDS].toSorted().join(", ")}`
415
858
  };
859
+ if (!supportsCopilotByokProviderShape({
860
+ api: ctx.modelProvider?.api,
861
+ baseUrl: ctx.modelProvider?.baseUrl,
862
+ requestProxy: ctx.modelProvider?.request?.proxy,
863
+ requestTls: ctx.modelProvider?.request?.tls,
864
+ requestAllowPrivateNetwork: ctx.modelProvider?.request?.allowPrivateNetwork
865
+ })) return {
866
+ supported: false,
867
+ reason: "provider is not a supported Copilot BYOK model (requires supported api, baseUrl, and no request transport policy overrides)"
868
+ };
416
869
  return {
417
870
  supported: true,
418
871
  priority: 100
@@ -421,11 +874,17 @@ function createCopilotAgentHarness(options) {
421
874
  async runAttempt(params) {
422
875
  const attemptPromise = (async () => {
423
876
  if (disposed) throw new Error("[copilot] harness has been disposed; cannot start new attempts");
424
- const { resolvePoolAcquire, runCopilotAttempt } = await import("./attempt-ArBh6GPT.js");
877
+ const { resolvePoolAcquire, runCopilotAttempt } = await import("./attempt-DIieBZcc.js");
425
878
  if (disposed) throw new Error("[copilot] harness was disposed while starting an attempt");
426
- const poolAcquire = resolvePoolAcquire(params);
427
879
  const pool = await getPool();
428
880
  if (disposed) throw new Error("[copilot] harness was disposed while starting an attempt");
881
+ let poolAcquire;
882
+ try {
883
+ poolAcquire = resolvePoolAcquire(params);
884
+ } catch (error) {
885
+ if (isCopilotByokUnsupportedProviderError(error)) return runCopilotAttempt(params, { pool });
886
+ throw error;
887
+ }
429
888
  const openclawSessionId = typeof params.sessionId === "string" ? params.sessionId : void 0;
430
889
  const currentCompatKey = computeSessionCompatKey(params);
431
890
  const currentCompactKey = computeSessionCompactKey(params);
@@ -442,7 +901,7 @@ function createCopilotAgentHarness(options) {
442
901
  }
443
902
  } : params, {
444
903
  pool,
445
- onSessionEstablished: openclawSessionId ? ({ sdkSessionId, pooledClient, sessionConfig }) => {
904
+ onSessionEstablished: openclawSessionId ? ({ compactionSessionConfig, sdkSessionId, pooledClient, sessionConfig }) => {
446
905
  trackedSessions.set(openclawSessionId, {
447
906
  sdkSessionId,
448
907
  client: pooledClient.client,
@@ -450,7 +909,7 @@ function createCopilotAgentHarness(options) {
450
909
  compatKey: currentCompatKey,
451
910
  compactKey: currentCompactKey,
452
911
  poolKey: pooledClient.key,
453
- sessionConfig,
912
+ sessionConfig: compactionSessionConfig ?? sessionConfig,
454
913
  ...sessionAuthFields(poolAcquire.auth)
455
914
  });
456
915
  registerStoredBinding(options?.sessionStore, openclawSessionId, {
@@ -531,9 +990,21 @@ function createCopilotAgentHarness(options) {
531
990
  };
532
991
  const tracked = trackedSessions.get(openclawSessionId);
533
992
  const currentCompactKey = computeSessionCompactKey(params);
534
- const { resolvePoolAcquire } = await import("./attempt-ArBh6GPT.js");
535
- const resolvedPoolAcquire = resolvePoolAcquire(params);
536
- const currentAuth = sessionAuthFields(resolvedPoolAcquire.auth);
993
+ const { resolvePoolAcquire } = await import("./attempt-DIieBZcc.js");
994
+ let resolvedPoolAcquire;
995
+ let currentAuth;
996
+ try {
997
+ resolvedPoolAcquire = resolvePoolAcquire(params);
998
+ } catch (error) {
999
+ if (isCopilotByokUnsupportedProviderError(error)) return {
1000
+ ok: false,
1001
+ compacted: false,
1002
+ reason: "missing_thread_binding",
1003
+ failure: { reason: "missing_thread_binding" }
1004
+ };
1005
+ throw error;
1006
+ }
1007
+ if (!currentAuth) currentAuth = sessionAuthFields(resolvedPoolAcquire.auth);
537
1008
  const compatibleTracked = tracked?.compactKey === currentCompactKey && sessionAuthMatches(tracked, currentAuth) ? tracked : void 0;
538
1009
  if (!compatibleTracked) return {
539
1010
  ok: false,
@@ -541,20 +1012,30 @@ function createCopilotAgentHarness(options) {
541
1012
  reason: "missing_thread_binding",
542
1013
  failure: { reason: "missing_thread_binding" }
543
1014
  };
544
- const poolAcquire = compatibleTracked ? {
1015
+ const poolAcquire = {
545
1016
  key: compatibleTracked.poolKey,
546
1017
  options: compatibleTracked.clientOptions
547
- } : resolvedPoolAcquire;
1018
+ };
548
1019
  let compactResult;
549
1020
  let handle;
550
1021
  let pool;
551
1022
  let activeSdkSession;
1023
+ let cleanupByokProxy;
552
1024
  const hookContext = buildCopilotCompactionHookContext(params);
553
1025
  try {
554
1026
  throwIfAborted(params.abortSignal);
555
1027
  pool = await getPool();
556
1028
  handle = await pool.acquire(poolAcquire.key, poolAcquire.options);
557
1029
  const client = handle.client;
1030
+ const byokProxy = compatibleTracked.authMode === "byok" && compatibleTracked.sessionConfig.provider ? await createCopilotByokProxy({
1031
+ mode: "byok",
1032
+ provider: compatibleTracked.sessionConfig.provider
1033
+ }) : void 0;
1034
+ cleanupByokProxy = byokProxy?.close;
1035
+ const sessionConfig = byokProxy?.provider.provider ? {
1036
+ ...compatibleTracked.sessionConfig,
1037
+ provider: byokProxy.provider.provider
1038
+ } : compatibleTracked.sessionConfig;
558
1039
  await runAgentHarnessBeforeCompactionHook({
559
1040
  sessionFile: params.sessionFile,
560
1041
  ctx: hookContext
@@ -563,11 +1044,11 @@ function createCopilotAgentHarness(options) {
563
1044
  abortSignal,
564
1045
  client,
565
1046
  customInstructions: params.customInstructions,
566
- gitHubToken: compatibleTracked?.clientOptions.gitHubToken ?? (resolvedPoolAcquire.auth.authMode === "gitHubToken" ? resolvedPoolAcquire.auth.gitHubToken : void 0),
1047
+ gitHubToken: compatibleTracked?.clientOptions.gitHubToken ?? (resolvedPoolAcquire?.auth.authMode === "gitHubToken" ? resolvedPoolAcquire.auth.gitHubToken : void 0),
567
1048
  onSession: (session) => {
568
1049
  activeSdkSession = session;
569
1050
  },
570
- sessionConfig: compatibleTracked.sessionConfig,
1051
+ sessionConfig,
571
1052
  sdkSessionId: compatibleTracked.sdkSessionId
572
1053
  }), resolveCompactionTimeoutMs(params.config), {
573
1054
  abortSignal: params.abortSignal,
@@ -598,6 +1079,7 @@ function createCopilotAgentHarness(options) {
598
1079
  }
599
1080
  };
600
1081
  } finally {
1082
+ await cleanupByokProxy?.();
601
1083
  if (pool && handle) try {
602
1084
  await pool.release(handle);
603
1085
  } catch {}
@@ -648,4 +1130,4 @@ function createCopilotAgentHarness(options) {
648
1130
  };
649
1131
  }
650
1132
  //#endregion
651
- export { resolveCopilotAuth as n, createCopilotAgentHarness as t };
1133
+ export { resolveCopilotAuth as a, createCopilotByokAuth as i, resolveCopilotProvider as n, createCopilotByokProxy as r, createCopilotAgentHarness as t };
package/dist/harness.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as createCopilotAgentHarness } from "./harness-D-3KWchY.js";
1
+ import { t as createCopilotAgentHarness } from "./harness-B9xB4LpE.js";
2
2
  export { createCopilotAgentHarness };
package/dist/index.js CHANGED
@@ -1,9 +1,7 @@
1
- import { t as createCopilotAgentHarness } from "./harness-D-3KWchY.js";
1
+ import { t as createCopilotAgentHarness } from "./harness-B9xB4LpE.js";
2
2
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
3
+ import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
3
4
  //#region extensions/copilot/index.ts
4
- function isRecord(value) {
5
- return typeof value === "object" && value !== null && !Array.isArray(value);
6
- }
7
5
  function readPoolOptions(pluginConfig) {
8
6
  if (!isRecord(pluginConfig)) return;
9
7
  const pool = pluginConfig.pool;
@@ -17,6 +15,7 @@ var copilot_default = definePluginEntry({
17
15
  name: "GitHub Copilot agent runtime",
18
16
  description: "Registers the GitHub Copilot agent runtime.",
19
17
  register(api) {
18
+ if (api.registrationMode !== "full") return;
20
19
  const poolOptions = readPoolOptions(api.pluginConfig);
21
20
  const sessionStore = api.runtime.state.openSyncKeyedStore({
22
21
  namespace: "sdk-sessions",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openclaw/copilot",
3
- "version": "2026.6.11",
3
+ "version": "2026.7.1-beta.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/copilot",
9
- "version": "2026.6.11",
9
+ "version": "2026.7.1-beta.2",
10
10
  "dependencies": {
11
11
  "@github/copilot-sdk": "1.0.0-beta.9"
12
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/copilot",
3
- "version": "2026.6.11",
3
+ "version": "2026.7.1-beta.2",
4
4
  "description": "OpenClaw GitHub Copilot agent runtime plugin (registers a `github-copilot` AgentHarness backed by @github/copilot-sdk over JSON-RPC to the GitHub Copilot CLI)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,10 +25,10 @@
25
25
  "minHostVersion": ">=2026.5.28"
26
26
  },
27
27
  "compat": {
28
- "pluginApi": ">=2026.6.11"
28
+ "pluginApi": ">=2026.7.1-beta.2"
29
29
  },
30
30
  "build": {
31
- "openclawVersion": "2026.6.11",
31
+ "openclawVersion": "2026.7.1-beta.2",
32
32
  "bundledDist": false
33
33
  },
34
34
  "release": {
@@ -47,7 +47,7 @@
47
47
  "README.md"
48
48
  ],
49
49
  "peerDependencies": {
50
- "openclaw": ">=2026.6.11"
50
+ "openclaw": ">=2026.7.1-beta.2"
51
51
  },
52
52
  "peerDependenciesMeta": {
53
53
  "openclaw": {