diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index 53aa71fd2..7cca2fa10 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -423,10 +423,10 @@ export type InstalledPluginSource = * Category of instruction source — used for merge logic * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "InstructionsSourcesType". + * via the `definition` "InstructionSourceType". */ /** @experimental */ -export type InstructionsSourcesType = +export type InstructionSourceType = /** Instructions loaded from the user's home configuration. */ | "home" /** Instructions loaded from repository-scoped files. */ @@ -445,10 +445,10 @@ export type InstructionsSourcesType = * Where this source lives — used for UI grouping * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "InstructionsSourcesLocation". + * via the `definition` "InstructionSourceLocation". */ /** @experimental */ -export type InstructionsSourcesLocation = +export type InstructionSourceLocation = /** Instructions live in user-level configuration. */ | "user" /** Instructions live in repository-level configuration. */ @@ -1011,6 +1011,32 @@ export type ProviderConfigWireApi = | "completions" /** OpenAI Responses API wire format. */ | "responses"; +/** + * Provider family. Matches the `type` field of a BYOK provider config. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ProviderEndpointType". + */ +/** @experimental */ +export type ProviderEndpointType = + /** OpenAI-compatible endpoint (use the OpenAI client library). */ + | "openai" + /** Azure OpenAI endpoint (use the OpenAI client library with the Azure base URL). */ + | "azure" + /** Anthropic endpoint (use the Anthropic client library). */ + | "anthropic"; +/** + * Wire API to be used, when required for the provider type. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ProviderEndpointWireApi". + */ +/** @experimental */ +export type ProviderEndpointWireApi = + /** Classic chat-completions request shape. */ + | "completions" + /** Newer responses request shape. */ + | "responses"; /** * Schema for the `PushAttachment` type. * @@ -2011,6 +2037,23 @@ export interface AgentReloadResult { */ agents: AgentInfo[]; } +/** + * Optional project paths to include in agent discovery. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "AgentsDiscoverRequest". + */ +/** @experimental */ +export interface AgentsDiscoverRequest { + /** + * Optional list of project directory paths to scan for project-scoped agents. When omitted or empty, only user/plugin/remote-independent agents are returned (no project scan). + */ + projectPaths?: string[]; + /** + * When true, omit the host's agents (the `/agents` directory and all plugin agents), leaving only project and remote agents. For multitenant deployments. + */ + excludeHostAgents?: boolean; +} /** * Name of the custom agent to select for subsequent turns. * @@ -2823,6 +2866,10 @@ export interface SlashCommandInfo { * Whether the command is experimental */ experimental?: boolean; + /** + * Whether the command may be the target of `/every` / `/after` schedules. Resolution happens at every tick, so only set this when the command is safe to re-invoke and produces an agent prompt. + */ + schedulable?: boolean; } /** * Optional unstructured input hint @@ -3964,6 +4011,23 @@ export interface InstalledPluginInfo { */ enabled: boolean; } +/** + * Optional project paths to include in instruction discovery. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "InstructionsDiscoverRequest". + */ +/** @experimental */ +export interface InstructionsDiscoverRequest { + /** + * Optional list of project directory paths to scan for repository/working-directory instruction sources. When omitted or empty, only user-level and plugin instruction sources are returned (no project scan). + */ + projectPaths?: string[]; + /** + * When true, omit the host's instruction sources (user/home-level files and plugin rules), leaving only repository and working-directory sources. For multitenant deployments. + */ + excludeHostInstructions?: boolean; +} /** * Instruction sources loaded for the session, in merge order. * @@ -3975,16 +4039,16 @@ export interface InstructionsGetSourcesResult { /** * Instruction sources for the session */ - sources: InstructionsSources[]; + sources: InstructionSource[]; } /** - * Schema for the `InstructionsSources` type. + * Schema for the `InstructionSource` type. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "InstructionsSources". + * via the `definition` "InstructionSource". */ /** @experimental */ -export interface InstructionsSources { +export interface InstructionSource { /** * Unique identifier for this source (used for toggling) */ @@ -4001,8 +4065,8 @@ export interface InstructionsSources { * Raw content of the instruction file */ content: string; - type: InstructionsSourcesType; - location: InstructionsSourcesLocation; + type: InstructionSourceType; + location: InstructionSourceLocation; /** * Glob pattern(s) from frontmatter — when set, this instruction applies only to matching files */ @@ -4015,6 +4079,10 @@ export interface InstructionsSources { * When true, this source starts disabled and must be toggled on by the user */ defaultDisabled?: boolean; + /** + * The project path this source was discovered from. Only set by sessionless discovery for repository/working-directory sources, where it disambiguates same-named files (e.g. .github/copilot-instructions.md) across multiple workspace roots. The session-scoped getSources leaves it unset. + */ + projectPath?: string; } /** * Schema for the `LocalSessionMetadataValue` type. @@ -5345,6 +5413,19 @@ export interface McpUnregisterExternalClientRequest { */ serverName: string; } +/** + * Memory configuration for this session. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "MemoryConfiguration". + */ +/** @experimental */ +export interface MemoryConfiguration { + /** + * Whether memory is enabled for the session. + */ + enabled: boolean; +} /** * Model identifier and token limits used to compute the context-info breakdown. * @@ -7771,6 +7852,70 @@ export interface ProviderConfigAzure { */ apiVersion?: string; } +/** + * A snapshot of the provider endpoint the session is currently configured to talk to. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ProviderEndpoint". + */ +/** @experimental */ +export interface ProviderEndpoint { + type: ProviderEndpointType; + wireApi?: ProviderEndpointWireApi; + /** + * Base URL to pass to the LLM client library. + */ + baseUrl: string; + /** + * A credential the caller should use with this endpoint. Omitted only when the endpoint accepts unauthenticated requests. + */ + apiKey?: string; + /** + * HTTP headers the caller must include on every outbound request. + */ + headers: { + [k: string]: string | undefined; + }; + sessionToken?: ProviderSessionToken; +} +/** + * Short-lived, rotating credential the caller must send on every request, in addition to `apiKey` if one is present. Omitted when the endpoint does not require one. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ProviderSessionToken". + */ +/** @experimental */ +export interface ProviderSessionToken { + /** + * The short-lived token value. + */ + token: string; + /** + * HTTP header name the token must be sent under. + */ + header: string; + /** + * The model the token is bound to, when applicable. When set, the token is only valid for requests against this model. + */ + model?: string; + /** + * When the token expires, if known. Callers should refresh by calling `getEndpoint` again before this time, or reactively on any 401/403 response from `baseUrl`. + */ + expiresAt?: string; +} +/** + * Optional model identifier to scope the endpoint snapshot to. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ProviderGetEndpointRequest". + */ +/** @experimental */ +export interface ProviderGetEndpointRequest { + /** + * Model identifier the caller intends to use against the returned endpoint. Used to pick the correct wire shape. Omit to use whichever model the session is currently using. + */ + modelId?: string; +} /** * File attachment * @@ -8553,9 +8698,21 @@ export interface ScheduleEntry { */ id: number; /** - * Interval between scheduled ticks, in milliseconds. + * Interval between scheduled ticks, in milliseconds (relative-interval schedules). + */ + intervalMs?: number; + /** + * 5-field cron expression for a recurring calendar schedule, evaluated in `tz`. + */ + cron?: string; + /** + * IANA timezone the `cron` expression is evaluated in. + */ + tz?: string; + /** + * Absolute fire time (epoch milliseconds) for a one-shot calendar schedule. */ - intervalMs: number; + at?: number; /** * Prompt text that gets enqueued on every tick. */ @@ -8722,6 +8879,32 @@ export interface SendResult { */ messageId: string; } +/** + * Agents discovered across user, project, plugin, and remote sources. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ServerAgentList". + */ +/** @experimental */ +export interface ServerAgentList { + /** + * All discovered agents across all sources + */ + agents: AgentInfo[]; +} +/** + * Instruction sources discovered across user, repository, and plugin sources. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ServerInstructionSourceList". + */ +/** @experimental */ +export interface ServerInstructionSourceList { + /** + * All discovered instruction sources + */ + sources: InstructionSource[]; +} /** * Schema for the `ServerSkill` type. * @@ -9660,6 +9843,7 @@ export interface SessionOpenOptions { * @experimental */ additionalContentExclusionPolicies?: SessionOpenOptionsAdditionalContentExclusionPolicy[]; + memory?: MemoryConfiguration; /** * Capabilities enabled for this session. */ @@ -11112,9 +11296,13 @@ export interface TaskAgentInfo { */ result?: string; /** - * Model used for the task when specified + * Requested model override for the task when specified */ model?: string; + /** + * Runtime model resolved for the task when available + */ + resolvedModel?: string; executionMode?: TaskExecutionMode; /** * Whether the task is currently in the original sync wait and can be moved to background mode. False once it is already backgrounded, idle, finished, or no longer has a promotable sync waiter. @@ -12838,6 +13026,30 @@ export function createServerRpc(connection: MessageConnection) { discover: async (params: SkillsDiscoverRequest): Promise => connection.sendRequest("skills.discover", params), }, + /** @experimental */ + agents: { + /** + * Discovers custom agents across user, project, plugin, and remote sources. + * + * @param params Optional project paths to include in agent discovery. + * + * @returns Agents discovered across user, project, plugin, and remote sources. + */ + discover: async (params: AgentsDiscoverRequest): Promise => + connection.sendRequest("agents.discover", params), + }, + /** @experimental */ + instructions: { + /** + * Discovers instruction sources across user, repository, and plugin sources. + * + * @param params Optional project paths to include in instruction discovery. + * + * @returns Instruction sources discovered across user, repository, and plugin sources. + */ + discover: async (params: InstructionsDiscoverRequest): Promise => + connection.sendRequest("instructions.discover", params), + }, user: { settings: { /** @@ -13673,15 +13885,6 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ reload: async (): Promise => connection.sendRequest("session.mcp.reload", { sessionId }), - /** - * Reloads MCP server connections for the session with an explicit host-provided configuration. - * - * @param params Opaque MCP reload configuration. - * - * @returns MCP server startup filtering result. - */ - reloadWithConfig: async (params: McpReloadWithConfigRequest): Promise => - connection.sendRequest("session.mcp.reloadWithConfig", { sessionId, ...params }), /** * Runs an MCP sampling inference on behalf of an MCP server. * @@ -13716,29 +13919,6 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ removeGitHub: async (): Promise => connection.sendRequest("session.mcp.removeGitHub", { sessionId }), - /** - * Configures the built-in GitHub MCP server for the session's current auth context. - * - * @param params Opaque auth info used to configure GitHub MCP. - * - * @returns Result of configuring GitHub MCP. - */ - configureGitHub: async (params: McpConfigureGitHubRequest): Promise => - connection.sendRequest("session.mcp.configureGitHub", { sessionId, ...params }), - /** - * Starts an individual MCP server on the session's host. - * - * @param params Server name and opaque configuration for an individual MCP server start. - */ - startServer: async (params: McpStartServerRequest): Promise => - connection.sendRequest("session.mcp.startServer", { sessionId, ...params }), - /** - * Restarts an individual MCP server on the session's host (stops then starts). - * - * @param params Server name and opaque configuration for an individual MCP server restart. - */ - restartServer: async (params: McpRestartServerRequest): Promise => - connection.sendRequest("session.mcp.restartServer", { sessionId, ...params }), /** * Stops an individual MCP server on the session's host. * @@ -13746,20 +13926,6 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ stopServer: async (params: McpStopServerRequest): Promise => connection.sendRequest("session.mcp.stopServer", { sessionId, ...params }), - /** - * Registers a pre-connected external MCP client (e.g. IDE) on the session's host. The caller retains lifecycle ownership of the client and transport. Marked internal because the `client` and `transport` arguments are in-process MCP SDK instances that cannot be serialized across the JSON-RPC boundary; once the CLI moves on top of the SDK, external clients will be expressed as transport configs the runtime can construct itself. - * - * @param params Registration parameters for an external MCP client. - */ - registerExternalClient: async (params: McpRegisterExternalClientRequest): Promise => - connection.sendRequest("session.mcp.registerExternalClient", { sessionId, ...params }), - /** - * Unregisters a previously registered external MCP client by server name. Marked internal as the paired companion of `registerExternalClient`: only in-process callers that registered a client this way can meaningfully unregister it. Disappears alongside `registerExternalClient`: once external clients are described to the runtime as config rather than handed in as instances, lifecycle (including deregistration) is owned entirely by the runtime. - * - * @param params Server name identifying the external client to remove. - */ - unregisterExternalClient: async (params: McpUnregisterExternalClientRequest): Promise => - connection.sendRequest("session.mcp.unregisterExternalClient", { sessionId, ...params }), /** * Checks whether a named MCP server is currently running on the session's host. * @@ -13771,15 +13937,6 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin connection.sendRequest("session.mcp.isServerRunning", { sessionId, ...params }), /** @experimental */ oauth: { - /** - * Responds to a pending MCP OAuth provider request. Marked internal because the `provider` argument is an in-process OAuthClientProvider instance that cannot be carried over the wire; the public OAuth surface will route the response through a wire-clean handshake once the CLI moves on top of the SDK. - * - * @param params MCP OAuth request id and optional provider response. - * - * @returns Empty result after recording the MCP OAuth response. - */ - respond: async (params: McpOauthRespondRequest): Promise => - connection.sendRequest("session.mcp.oauth.respond", { sessionId, ...params }), /** * Starts OAuth authentication for a remote MCP server. * @@ -13862,6 +14019,18 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin connection.sendRequest("session.plugins.reload", { sessionId, ...params }), }, /** @experimental */ + provider: { + /** + * Returns the provider endpoint and credentials the session is currently configured to talk to, so the caller can make inference calls directly against the same backend the session uses. + * + * @param params Optional model identifier to scope the endpoint snapshot to. + * + * @returns A snapshot of the provider endpoint the session is currently configured to talk to. + */ + getEndpoint: async (params?: ProviderGetEndpointRequest): Promise => + connection.sendRequest("session.provider.getEndpoint", { sessionId, ...params }), + }, + /** @experimental */ options: { /** * Patches the genuinely-mutable subset of session options. @@ -14566,6 +14735,77 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin }; } +/** + * Create typed session-scoped RPC methods that are part of the SDK's internal + * surface. Not exported on the public client API. + * @internal + */ +export function createInternalSessionRpc(connection: MessageConnection, sessionId: string) { + return { + /** @experimental */ + mcp: { + /** + * Reloads MCP server connections for the session with an explicit host-provided configuration. + * + * @param params Opaque MCP reload configuration. + * + * @returns MCP server startup filtering result. + */ + reloadWithConfig: async (params: McpReloadWithConfigRequest): Promise => + connection.sendRequest("session.mcp.reloadWithConfig", { sessionId, ...params }), + /** + * Configures the built-in GitHub MCP server for the session's current auth context. + * + * @param params Opaque auth info used to configure GitHub MCP. + * + * @returns Result of configuring GitHub MCP. + */ + configureGitHub: async (params: McpConfigureGitHubRequest): Promise => + connection.sendRequest("session.mcp.configureGitHub", { sessionId, ...params }), + /** + * Starts an individual MCP server on the session's host. + * + * @param params Server name and opaque configuration for an individual MCP server start. + */ + startServer: async (params: McpStartServerRequest): Promise => + connection.sendRequest("session.mcp.startServer", { sessionId, ...params }), + /** + * Restarts an individual MCP server on the session's host (stops then starts). + * + * @param params Server name and opaque configuration for an individual MCP server restart. + */ + restartServer: async (params: McpRestartServerRequest): Promise => + connection.sendRequest("session.mcp.restartServer", { sessionId, ...params }), + /** + * Registers a pre-connected external MCP client (e.g. IDE) on the session's host. The caller retains lifecycle ownership of the client and transport. Marked internal because the `client` and `transport` arguments are in-process MCP SDK instances that cannot be serialized across the JSON-RPC boundary; once the CLI moves on top of the SDK, external clients will be expressed as transport configs the runtime can construct itself. + * + * @param params Registration parameters for an external MCP client. + */ + registerExternalClient: async (params: McpRegisterExternalClientRequest): Promise => + connection.sendRequest("session.mcp.registerExternalClient", { sessionId, ...params }), + /** + * Unregisters a previously registered external MCP client by server name. Marked internal as the paired companion of `registerExternalClient`: only in-process callers that registered a client this way can meaningfully unregister it. Disappears alongside `registerExternalClient`: once external clients are described to the runtime as config rather than handed in as instances, lifecycle (including deregistration) is owned entirely by the runtime. + * + * @param params Server name identifying the external client to remove. + */ + unregisterExternalClient: async (params: McpUnregisterExternalClientRequest): Promise => + connection.sendRequest("session.mcp.unregisterExternalClient", { sessionId, ...params }), + /** @experimental */ + oauth: { + /** + * Responds to a pending MCP OAuth provider request. Marked internal because the `provider` argument is an in-process OAuthClientProvider instance that cannot be carried over the wire; the public OAuth surface will route the response through a wire-clean handshake once the CLI moves on top of the SDK. + * + * @param params MCP OAuth request id and optional provider response. + * + * @returns Empty result after recording the MCP OAuth response. + */ + respond: async (params: McpOauthRespondRequest): Promise => + connection.sendRequest("session.mcp.oauth.respond", { sessionId, ...params }), + }, + }, + }; +} + /** Handler for `sessionFs` client session API methods. */ /** @experimental */ export interface SessionFsHandler { diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 77a354cb6..e219371d3 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -715,6 +715,10 @@ export interface ResumeData { * Total number of persisted events in the session at the time of resume */ eventCount: number; + /** + * On-disk byte size of the session's persisted events.jsonl file at resume time; omitted when the file does not exist or cannot be stat'd + */ + eventsFileSizeBytes?: number; /** * Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max") */ @@ -959,6 +963,14 @@ export interface ScheduleCreatedEvent { * Scheduled prompt registered via /every or /after */ export interface ScheduleCreatedData { + /** + * Absolute fire time (epoch milliseconds) for a one-shot calendar schedule + */ + at?: number; + /** + * 5-field cron expression for a recurring calendar schedule, evaluated in `tz` + */ + cron?: string; /** * Optional user-facing label shown in the timeline instead of the actual prompt (e.g. `/skill-name args` when the prompt is a skill invocation expansion) */ @@ -968,9 +980,9 @@ export interface ScheduleCreatedData { */ id: number; /** - * Interval between ticks in milliseconds + * Interval between ticks in milliseconds (relative-interval schedules) */ - intervalMs: number; + intervalMs?: number; /** * Prompt text that gets enqueued on every tick */ @@ -979,6 +991,10 @@ export interface ScheduleCreatedData { * Whether the schedule re-arms after each tick (`/every`) or fires once (`/after`) */ recurring?: boolean; + /** + * IANA timezone the `cron` expression is evaluated in + */ + tz?: string; } /** * Session event "session.schedule_cancelled". Scheduled prompt cancelled from the schedule manager dialog @@ -1610,6 +1626,10 @@ export interface ShutdownData { * Error description when shutdownType is "error" */ errorReason?: string; + /** + * On-disk byte size of the session's persisted events.jsonl file at shutdown time; omitted when the file does not exist or cannot be stat'd + */ + eventsFileSizeBytes?: number; /** * Per-model usage breakdown, keyed by model identifier */ @@ -2633,18 +2653,6 @@ export interface AssistantMessageEvent { * Assistant response containing text content, optional tool requests, and interaction metadata */ export interface AssistantMessageData { - /** - * Raw Anthropic content array with advisor blocks (server_tool_use, advisor_tool_result) for verbatim round-tripping - * - * @experimental - */ - anthropicAdvisorBlocks?: unknown[]; - /** - * Anthropic advisor model ID used for this response, for timeline display on replay - * - * @experimental - */ - anthropicAdvisorModel?: string; /** * Provider's completion / response identifier; shared across all chunks of a single API call. Used to group multi-chunk assistant utterances. */ @@ -2694,6 +2702,7 @@ export interface AssistantMessageData { * GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs */ requestId?: string; + serverTools?: AssistantMessageServerTools; /** * Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation */ @@ -2707,6 +2716,19 @@ export interface AssistantMessageData { */ turnId?: string; } +/** + * Neutral provider-tagged server-side tool-use payload (tool search, advisor) for verbatim round-tripping + */ +/** @experimental */ +export interface AssistantMessageServerTools { + advisorModel?: string; + functionCallNamespaces?: { + [k: string]: string | undefined; + }; + items?: unknown[]; + provider: string; + rawContentBlocks?: unknown[]; +} /** * A tool invocation request from the assistant */ @@ -3842,7 +3864,7 @@ export interface SkillInvokedData { */ pluginVersion?: string; /** - * Source identifier for where the skill was discovered. Known values include: project (workspace skill), inherited (parent-directory skill), personal-copilot (~/.copilot/skills), personal-agents (~/.agents/skills), personal-claude (~/.claude/skills), custom (configured directory), plugin (installed plugin), builtin (bundled runtime skill), and remote (org/enterprise skill) + * Source identifier for where the skill was discovered. Known values include: project (workspace skill), inherited (parent-directory skill), personal-copilot (~/.copilot/skills), personal-agents (~/.agents/skills), custom (configured directory), plugin (installed plugin), builtin (bundled runtime skill), and remote (org/enterprise skill) */ source?: string; trigger?: SkillInvokedTrigger; @@ -4271,6 +4293,10 @@ export interface HookProgressData { * Human-readable progress message from the hook process */ message: string; + /** + * When true, this status message replaces the previous temporary one instead of accumulating + */ + temporary?: boolean; } /** * Session event "system.message". System/developer instruction content with role and optional template metadata diff --git a/nodejs/test/e2e/provider_endpoint.e2e.test.ts b/nodejs/test/e2e/provider_endpoint.e2e.test.ts new file mode 100644 index 000000000..1bac76253 --- /dev/null +++ b/nodejs/test/e2e/provider_endpoint.e2e.test.ts @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { describe, expect, it } from "vitest"; +import { approveAll } from "../../src/index.js"; +import { createSdkTestContext } from "./harness/sdkTestContext.js"; + +describe("session.provider.getEndpoint RPC", async () => { + const { copilotClient: client, env } = await createSdkTestContext(); + + // The provider endpoint API is gated behind an opt-in env var; the harness + // env object is the same one passed to the CLI subprocess, so mutating it + // here enables the API for this test file's client. + env.COPILOT_ALLOW_GET_PROVIDER_ENDPOINT = "true"; + + it("returns the BYOK provider endpoint when a custom provider is configured", async () => { + const session = await client.createSession({ + onPermissionRequest: approveAll, + provider: { + type: "openai", + wireApi: "completions", + baseUrl: "https://api.example.test/v1", + apiKey: "byok-secret", + headers: { "X-Custom-Header": "byok-yes" }, + }, + }); + + try { + const endpoint = await session.rpc.provider.getEndpoint({}); + + expect(endpoint.type).toBe("openai"); + expect(endpoint.wireApi).toBe("completions"); + expect(endpoint.baseUrl).toBe("https://api.example.test/v1"); + expect(endpoint.apiKey).toBe("byok-secret"); + expect(endpoint.headers).toMatchObject({ "X-Custom-Header": "byok-yes" }); + // BYOK sessions never issue a CAPI session token. + expect(endpoint.sessionToken).toBeUndefined(); + } finally { + try { + await session.disconnect(); + } catch { + // disconnect may fail since the BYOK provider URL is fake + } + } + }); + + it("returns the CAPI provider endpoint for an OAuth-authenticated session", async () => { + const session = await client.createSession({ + onPermissionRequest: approveAll, + }); + + try { + const endpoint = await session.rpc.provider.getEndpoint({}); + + expect(["openai", "azure", "anthropic"]).toContain(endpoint.type); + // wireApi is omitted for anthropic; otherwise one of the OpenAI shapes. + if (endpoint.type !== "anthropic") { + expect(["completions", "responses"]).toContain(endpoint.wireApi); + } + + // CAPI baseUrl is the (proxy) Copilot API URL injected by the harness. + expect(endpoint.baseUrl).toMatch(/^https?:\/\//); + + // For CAPI OAuth sessions the apiKey is the resolved GitHub bearer. + expect(endpoint.apiKey).toBeTypeOf("string"); + expect(endpoint.apiKey!.length).toBeGreaterThan(0); + + // Standard CAPI headers should be present, and Authorization is + // surfaced as the runtime sends it (`Bearer `). + expect(endpoint.headers["Copilot-Integration-Id"]).toBeTypeOf("string"); + expect(endpoint.headers["User-Agent"]).toMatch(/Copilot/i); + expect(endpoint.headers["X-GitHub-Api-Version"]).toBeTypeOf("string"); + expect(endpoint.headers["X-Interaction-Id"]).toMatch(/[0-9a-f-]{8,}/); + expect(endpoint.headers.Authorization).toBe(`Bearer ${endpoint.apiKey}`); + + // When the omit-modelId path returned an auto-mode session token, it + // must use the documented header name and an ISO 8601 expiry. The + // harness may have a non-auto model selected, in which case the + // field is simply omitted. + if (endpoint.sessionToken) { + expect(endpoint.sessionToken.header).toBe("Copilot-Session-Token"); + expect(endpoint.sessionToken.token.length).toBeGreaterThan(0); + if (endpoint.sessionToken.expiresAt !== undefined) { + expect(Date.parse(endpoint.sessionToken.expiresAt)).not.toBeNaN(); + } + } + } finally { + await session.disconnect(); + } + }); +});