Skip to content

Commit 2546a31

Browse files
committed
fix: send x-opencode-session on opencode gateway requests
1 parent 69284bf commit 2546a31

16 files changed

Lines changed: 173 additions & 8 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pymodel/pythinker-code": patch
3+
---
4+
5+
Send OpenCode Go requests with a per-conversation session header.

packages/agent-core-v2/src/agent/profile/profileService.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,7 @@ export class AgentProfileService extends Disposable implements IAgentProfileServ
464464
};
465465
return {
466466
cacheKey: this.sessionContext.sessionId,
467+
conversationId: this.sessionContext.sessionId,
467468
sampling:
468469
sampling.temperature === undefined && sampling.topP === undefined ? undefined : sampling,
469470
thinkingEffort: thinking.effective,

packages/agent-core-v2/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ export * from '#/kosong/model/thinking';
199199
export * from '#/kosong/model/catalog';
200200
export * from '#/kosong/model/catalogService';
201201
export * from '#/kosong/model/modelRequester';
202+
export * from '#/kosong/model/opencodeSession';
202203
import '#/kosong/model/errors';
203204
export {
204205
MODEL_CATALOG_SECTION,

packages/agent-core-v2/src/kosong/contract/provider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export interface VideoUploadInput {
7373
export interface GenerateOptions {
7474
signal?: AbortSignal;
7575
auth?: ProviderRequestAuth;
76+
extraHeaders?: Readonly<Record<string, string>>;
7677
responseFormat?: ResponseFormat;
7778
cacheKey?: string;
7879
sampling?: SamplingOptions;

packages/agent-core-v2/src/kosong/model/catalogService.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { parsePythinkerCodeCustomHeaders } from '@pymodel/pythinker-code-oauth';
2+
import { randomUUID } from 'node:crypto';
23

34
import { Disposable } from '#/_base/di/lifecycle';
45
import { LifecycleScope } from '#/app/scopes';
@@ -149,7 +150,7 @@ export class ModelCatalog extends Disposable implements IModelCatalog {
149150
messages: [{ role: 'user', content: [{ type: 'text', text: 'ping' }], toolCalls: [] }],
150151
},
151152
undefined,
152-
{ maxCompletionTokens: 512 },
153+
{ conversationId: randomUUID(), maxCompletionTokens: 512 },
153154
)) {
154155
if (event.type === 'part' && event.part.type === 'text') {
155156
text += event.part.text;

packages/agent-core-v2/src/kosong/model/modelRequester.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export type ModelRequestEvent =
4242

4343
export interface ModelRequestParams {
4444
readonly cacheKey?: string;
45+
readonly conversationId?: string;
4546
readonly sampling?: SamplingOptions;
4647
readonly thinkingEffort?: ThinkingEffort;
4748
readonly thinkingKeep?: string;

packages/agent-core-v2/src/kosong/model/modelRequesterImpl.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import type {
1212
import { translateProviderError } from '#/kosong/protocol/errors';
1313
import type { IProtocolAdapterRegistry } from '#/kosong/protocol/protocol';
1414

15+
import { opencodeSessionHeaders } from './opencodeSession';
16+
1517
import type { AuthProvider, Model } from './catalog';
1618
import type {
1719
ModelRequestEvent,
@@ -90,6 +92,7 @@ export class ModelRequesterImpl implements ModelRequester {
9092
const options: GenerateOptions = {
9193
signal,
9294
cacheKey: params?.cacheKey,
95+
extraHeaders: opencodeSessionHeaders(this.model.baseUrl, params?.conversationId),
9396
sampling: params?.sampling,
9497
thinking:
9598
params?.thinkingEffort === undefined
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const OPENCODE_HOST = 'opencode.ai';
2+
3+
export const OPENCODE_SESSION_HEADER = 'x-opencode-session';
4+
5+
export function isOpencodeGatewayBaseUrl(baseUrl: string | undefined): boolean {
6+
if (baseUrl === undefined || baseUrl.length === 0) return false;
7+
let host: string;
8+
try {
9+
host = new URL(baseUrl).hostname.toLowerCase();
10+
} catch {
11+
return false;
12+
}
13+
return host === OPENCODE_HOST || host.endsWith(`.${OPENCODE_HOST}`);
14+
}
15+
16+
export function opencodeSessionHeaders(
17+
baseUrl: string | undefined,
18+
conversationId: string | undefined,
19+
): Record<string, string> | undefined {
20+
const id = conversationId?.trim();
21+
if (!isOpencodeGatewayBaseUrl(baseUrl) || id === undefined || id.length === 0) return undefined;
22+
return { [OPENCODE_SESSION_HEADER]: id };
23+
}

packages/agent-core-v2/src/kosong/provider/bases/anthropic/anthropic.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -979,7 +979,10 @@ export class AnthropicChatProvider implements ChatProvider {
979979
}
980980

981981
const requestOptions: Record<string, unknown> = {};
982-
const headers = mergeRequestHeaders(extraHeaders, options?.auth?.headers);
982+
const headers = mergeRequestHeaders(
983+
mergeRequestHeaders(extraHeaders, options?.extraHeaders),
984+
options?.auth?.headers,
985+
);
983986
if (headers !== undefined) {
984987
requestOptions['headers'] = headers;
985988
}

packages/agent-core-v2/src/kosong/provider/bases/google-genai/google-genai.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ import type { Tool } from '#/kosong/contract/tool';
2121
import type { TokenUsage } from '#/kosong/contract/usage';
2222

2323
import { mergeConsecutiveUserMessages } from '../merge-user-messages';
24-
import { requireProviderApiKey, resolveAuthBackedClient } from '../request-auth';
24+
import {
25+
mergeRequestHeaders,
26+
requireProviderApiKey,
27+
resolveAuthBackedClient,
28+
} from '../request-auth';
2529

2630
function normalizeGoogleGenAIFinishReason(raw: unknown): {
2731
finishReason: FinishReason | null;
@@ -783,6 +787,11 @@ export class GoogleGenAIChatProvider implements ChatProvider {
783787
...(tools.length > 0 ? { tools: tools.map((t) => toolToGoogleGenAI(t)) } : {}),
784788
};
785789
applyResponseFormat(config, options?.responseFormat);
790+
if (options?.extraHeaders !== undefined) {
791+
config['httpOptions'] = {
792+
headers: mergeRequestHeaders(this._defaultHeaders, options.extraHeaders),
793+
};
794+
}
786795

787796
try {
788797
const client = this._createClient(options?.auth);

0 commit comments

Comments
 (0)