@terminai/core 0.23.0 → 0.25.0
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/dist/src/audit/redaction.js +24 -0
- package/dist/src/audit/redaction.js.map +1 -1
- package/dist/src/audit/redaction.test.d.ts +7 -0
- package/dist/src/audit/redaction.test.js +45 -0
- package/dist/src/audit/redaction.test.js.map +1 -0
- package/dist/src/auth/providerRegistry.js +20 -0
- package/dist/src/auth/providerRegistry.js.map +1 -1
- package/dist/src/auth/wizardSettings.d.ts +5 -0
- package/dist/src/auth/wizardSettings.js +43 -0
- package/dist/src/auth/wizardSettings.js.map +1 -1
- package/dist/src/auth/wizardSettings.test.js +20 -0
- package/dist/src/auth/wizardSettings.test.js.map +1 -1
- package/dist/src/auth/wizardState.d.ts +1 -1
- package/dist/src/auth/wizardState.js +2 -0
- package/dist/src/auth/wizardState.js.map +1 -1
- package/dist/src/auth/wizardState.test.js +23 -0
- package/dist/src/auth/wizardState.test.js.map +1 -1
- package/dist/src/config/builder.js +23 -0
- package/dist/src/config/builder.js.map +1 -1
- package/dist/src/config/config.js +40 -1
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/settings/schema.d.ts +50 -0
- package/dist/src/config/settings/schema.js +48 -0
- package/dist/src/config/settings/schema.js.map +1 -1
- package/dist/src/core/chatGptCodexContentGenerator.d.ts +35 -0
- package/dist/src/core/chatGptCodexContentGenerator.js +605 -0
- package/dist/src/core/chatGptCodexContentGenerator.js.map +1 -0
- package/dist/src/core/chatGptCodexContentGenerator.test.d.ts +7 -0
- package/dist/src/core/chatGptCodexContentGenerator.test.js +250 -0
- package/dist/src/core/chatGptCodexContentGenerator.test.js.map +1 -0
- package/dist/src/core/contentGenerator.d.ts +2 -1
- package/dist/src/core/contentGenerator.js +10 -0
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/contentGenerator.test.js +36 -0
- package/dist/src/core/contentGenerator.test.js.map +1 -1
- package/dist/src/core/loggingContentGenerator.d.ts +1 -0
- package/dist/src/core/loggingContentGenerator.js +30 -0
- package/dist/src/core/loggingContentGenerator.js.map +1 -1
- package/dist/src/core/loggingContentGenerator.test.js +62 -0
- package/dist/src/core/loggingContentGenerator.test.js.map +1 -1
- package/dist/src/core/openaiContentGenerator.d.ts +16 -0
- package/dist/src/core/openaiContentGenerator.js +244 -52
- package/dist/src/core/openaiContentGenerator.js.map +1 -1
- package/dist/src/core/openaiContentGenerator.test.js +176 -7
- package/dist/src/core/openaiContentGenerator.test.js.map +1 -1
- package/dist/src/core/providerTypes.d.ts +17 -1
- package/dist/src/core/providerTypes.js +10 -0
- package/dist/src/core/providerTypes.js.map +1 -1
- package/dist/src/core/providerTypes.test.js +11 -0
- package/dist/src/core/providerTypes.test.js.map +1 -1
- package/dist/src/core/turn.js +1 -1
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/core/turn.test.js +1 -1
- package/dist/src/core/turn.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +7 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/openai_chatgpt/constants.d.ts +23 -0
- package/dist/src/openai_chatgpt/constants.js +35 -0
- package/dist/src/openai_chatgpt/constants.js.map +1 -0
- package/dist/src/openai_chatgpt/credentialStorage.d.ts +18 -0
- package/dist/src/openai_chatgpt/credentialStorage.js +116 -0
- package/dist/src/openai_chatgpt/credentialStorage.js.map +1 -0
- package/dist/src/openai_chatgpt/credentialStorage.test.d.ts +7 -0
- package/dist/src/openai_chatgpt/credentialStorage.test.js +73 -0
- package/dist/src/openai_chatgpt/credentialStorage.test.js.map +1 -0
- package/dist/src/openai_chatgpt/imports.d.ts +10 -0
- package/dist/src/openai_chatgpt/imports.js +164 -0
- package/dist/src/openai_chatgpt/imports.js.map +1 -0
- package/dist/src/openai_chatgpt/imports.test.d.ts +7 -0
- package/dist/src/openai_chatgpt/imports.test.js +82 -0
- package/dist/src/openai_chatgpt/imports.test.js.map +1 -0
- package/dist/src/openai_chatgpt/jwt.d.ts +7 -0
- package/dist/src/openai_chatgpt/jwt.js +31 -0
- package/dist/src/openai_chatgpt/jwt.js.map +1 -0
- package/dist/src/openai_chatgpt/jwt.test.d.ts +7 -0
- package/dist/src/openai_chatgpt/jwt.test.js +23 -0
- package/dist/src/openai_chatgpt/jwt.test.js.map +1 -0
- package/dist/src/openai_chatgpt/oauthClient.d.ts +52 -0
- package/dist/src/openai_chatgpt/oauthClient.js +196 -0
- package/dist/src/openai_chatgpt/oauthClient.js.map +1 -0
- package/dist/src/openai_chatgpt/oauthClient.test.d.ts +7 -0
- package/dist/src/openai_chatgpt/oauthClient.test.js +138 -0
- package/dist/src/openai_chatgpt/oauthClient.test.js.map +1 -0
- package/dist/src/openai_chatgpt/types.d.ts +34 -0
- package/dist/src/openai_chatgpt/types.js +8 -0
- package/dist/src/openai_chatgpt/types.js.map +1 -0
- package/dist/src/utils/errorReporting.js +1 -1
- package/dist/src/utils/errorReporting.js.map +1 -1
- package/dist/src/utils/errorReporting.test.js +1 -1
- package/dist/src/utils/errorReporting.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -19,6 +19,7 @@ vi.mock('../telemetry/trace.js', () => ({
|
|
|
19
19
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
20
20
|
import { LoggingContentGenerator } from './loggingContentGenerator.js';
|
|
21
21
|
import { ApiRequestEvent } from '../telemetry/types.js';
|
|
22
|
+
import { LlmProviderId } from './providerTypes.js';
|
|
22
23
|
describe('LoggingContentGenerator', () => {
|
|
23
24
|
let wrapped;
|
|
24
25
|
let config;
|
|
@@ -33,6 +34,9 @@ describe('LoggingContentGenerator', () => {
|
|
|
33
34
|
config = {
|
|
34
35
|
getGoogleAIConfig: vi.fn(),
|
|
35
36
|
getVertexAIConfig: vi.fn(),
|
|
37
|
+
getProviderConfig: vi
|
|
38
|
+
.fn()
|
|
39
|
+
.mockReturnValue({ provider: LlmProviderId.GEMINI }),
|
|
36
40
|
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
|
37
41
|
authType: 'API_KEY',
|
|
38
42
|
}),
|
|
@@ -75,6 +79,35 @@ describe('LoggingContentGenerator', () => {
|
|
|
75
79
|
const responseEvent = vi.mocked(logApiResponse).mock.calls[0][1];
|
|
76
80
|
expect(responseEvent.duration_ms).toBe(1000);
|
|
77
81
|
});
|
|
82
|
+
it('should attribute server details for OpenAI-compatible providers', async () => {
|
|
83
|
+
vi.mocked(config.getProviderConfig).mockReturnValue({
|
|
84
|
+
provider: LlmProviderId.OPENAI_COMPATIBLE,
|
|
85
|
+
baseUrl: 'https://openrouter.ai/api/v1',
|
|
86
|
+
model: 'gpt-4o-mini',
|
|
87
|
+
});
|
|
88
|
+
const req = {
|
|
89
|
+
contents: [{ role: 'user', parts: [{ text: 'hello' }] }],
|
|
90
|
+
model: 'gpt-4o-mini',
|
|
91
|
+
};
|
|
92
|
+
const userPromptId = 'prompt-123';
|
|
93
|
+
const response = {
|
|
94
|
+
candidates: [],
|
|
95
|
+
usageMetadata: undefined,
|
|
96
|
+
text: undefined,
|
|
97
|
+
functionCalls: undefined,
|
|
98
|
+
executableCode: undefined,
|
|
99
|
+
codeExecutionResult: undefined,
|
|
100
|
+
data: undefined,
|
|
101
|
+
};
|
|
102
|
+
vi.mocked(wrapped.generateContent).mockResolvedValue(response);
|
|
103
|
+
await loggingContentGenerator.generateContent(req, userPromptId);
|
|
104
|
+
const requestEvent = vi.mocked(logApiRequest).mock.calls[0][1];
|
|
105
|
+
expect(requestEvent).toBeInstanceOf(ApiRequestEvent);
|
|
106
|
+
expect(requestEvent.prompt.server).toEqual({
|
|
107
|
+
address: 'openrouter.ai',
|
|
108
|
+
port: 443,
|
|
109
|
+
});
|
|
110
|
+
});
|
|
78
111
|
it('should log error on failure', async () => {
|
|
79
112
|
const req = {
|
|
80
113
|
contents: [{ role: 'user', parts: [{ text: 'hello' }] }],
|
|
@@ -124,6 +157,35 @@ describe('LoggingContentGenerator', () => {
|
|
|
124
157
|
const responseEvent = vi.mocked(logApiResponse).mock.calls[0][1];
|
|
125
158
|
expect(responseEvent.duration_ms).toBe(1000);
|
|
126
159
|
});
|
|
160
|
+
it('should attribute server details for OpenAI-compatible streaming calls', async () => {
|
|
161
|
+
vi.mocked(config.getProviderConfig).mockReturnValue({
|
|
162
|
+
provider: LlmProviderId.OPENAI_COMPATIBLE,
|
|
163
|
+
baseUrl: 'http://localhost:1234/v1',
|
|
164
|
+
model: 'gpt-4o-mini',
|
|
165
|
+
});
|
|
166
|
+
const req = {
|
|
167
|
+
contents: [{ role: 'user', parts: [{ text: 'hello' }] }],
|
|
168
|
+
model: 'gpt-4o-mini',
|
|
169
|
+
};
|
|
170
|
+
const userPromptId = 'prompt-123';
|
|
171
|
+
const response = {
|
|
172
|
+
candidates: [],
|
|
173
|
+
usageMetadata: undefined,
|
|
174
|
+
};
|
|
175
|
+
async function* createAsyncGenerator() {
|
|
176
|
+
yield response;
|
|
177
|
+
}
|
|
178
|
+
vi.mocked(wrapped.generateContentStream).mockResolvedValue(createAsyncGenerator());
|
|
179
|
+
const stream = await loggingContentGenerator.generateContentStream(req, userPromptId);
|
|
180
|
+
for await (const _ of stream) {
|
|
181
|
+
// consume stream
|
|
182
|
+
}
|
|
183
|
+
const requestEvent = vi.mocked(logApiRequest).mock.calls[0][1];
|
|
184
|
+
expect(requestEvent.prompt.server).toEqual({
|
|
185
|
+
address: 'localhost',
|
|
186
|
+
port: 1234,
|
|
187
|
+
});
|
|
188
|
+
});
|
|
127
189
|
it('should log error on failure', async () => {
|
|
128
190
|
const req = {
|
|
129
191
|
contents: [{ role: 'user', parts: [{ text: 'hello' }] }],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loggingContentGenerator.test.js","sourceRoot":"","sources":["../../../src/core/loggingContentGenerator.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,aAAa,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AAChD,MAAM,cAAc,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AACjD,MAAM,WAAW,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AAE9C,EAAE,CAAC,IAAI,CAAC,yBAAyB,EAAE,GAAG,EAAE,CAAC,CAAC;IACxC,aAAa;IACb,cAAc;IACd,WAAW;CACZ,CAAC,CAAC,CAAC;AAEJ,MAAM,iBAAiB,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CACxC,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAClE,CAAC;AAEF,EAAE,CAAC,IAAI,CAAC,uBAAuB,EAAE,GAAG,EAAE,CAAC,CAAC;IACtC,iBAAiB;CAClB,CAAC,CAAC,CAAC;AAEJ,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAMzE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"loggingContentGenerator.test.js","sourceRoot":"","sources":["../../../src/core/loggingContentGenerator.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,9 +11,13 @@ import type { Config } from '../config/config.js';
|
|
|
11
11
|
export declare class OpenAIContentGenerator implements ContentGenerator {
|
|
12
12
|
private readonly config;
|
|
13
13
|
private readonly globalConfig;
|
|
14
|
+
private toolNameMap;
|
|
14
15
|
constructor(config: OpenAICompatibleConfig, globalConfig: Config);
|
|
15
16
|
private hasUnsupportedModalities;
|
|
16
17
|
private partUnionToText;
|
|
18
|
+
private isPlainObject;
|
|
19
|
+
private getParametersJsonSchema;
|
|
20
|
+
private parseToolArguments;
|
|
17
21
|
private contentUnionToText;
|
|
18
22
|
private normalizeTools;
|
|
19
23
|
generateContent(request: GenerateContentParameters, _userPromptId: string): Promise<GenerateContentResponse>;
|
|
@@ -22,6 +26,18 @@ export declare class OpenAIContentGenerator implements ContentGenerator {
|
|
|
22
26
|
embedContent(_request: EmbedContentParameters): Promise<EmbedContentResponse>;
|
|
23
27
|
private fetchOpenAI;
|
|
24
28
|
private convertTools;
|
|
29
|
+
/**
|
|
30
|
+
* Sanitizes tool names for OpenAI function calling compatibility.
|
|
31
|
+
* OpenAI spec only allows: ^[a-zA-Z0-9_-]+$ (no dots, must not start with number)
|
|
32
|
+
* This converts names like "ui.click" → "ui_click" at the adapter level,
|
|
33
|
+
* keeping Gemini tool names unchanged in the core codebase.
|
|
34
|
+
* Records the mapping so we can reverse it when parsing model responses.
|
|
35
|
+
*/
|
|
36
|
+
private sanitizeToolName;
|
|
37
|
+
/**
|
|
38
|
+
* Reverses tool name sanitization to get original Gemini name.
|
|
39
|
+
*/
|
|
40
|
+
private unsanitizeToolName;
|
|
25
41
|
private convertSchemaToOpenAI;
|
|
26
42
|
private convertContentsToOpenAIMessages;
|
|
27
43
|
private convertOpenAIResponseToGemini;
|
|
@@ -11,6 +11,9 @@ import { estimateTokenCountSync } from '../utils/tokenCalculation.js';
|
|
|
11
11
|
export class OpenAIContentGenerator {
|
|
12
12
|
config;
|
|
13
13
|
globalConfig;
|
|
14
|
+
// Maps sanitized OpenAI tool names back to original Gemini names
|
|
15
|
+
// e.g., "ui_click" → "ui.click"
|
|
16
|
+
toolNameMap = new Map();
|
|
14
17
|
constructor(config, globalConfig) {
|
|
15
18
|
this.config = config;
|
|
16
19
|
this.globalConfig = globalConfig;
|
|
@@ -41,6 +44,32 @@ export class OpenAIContentGenerator {
|
|
|
41
44
|
}
|
|
42
45
|
return '';
|
|
43
46
|
}
|
|
47
|
+
isPlainObject(value) {
|
|
48
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
49
|
+
}
|
|
50
|
+
getParametersJsonSchema(fn) {
|
|
51
|
+
if (!this.isPlainObject(fn)) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const parametersJsonSchema = fn['parametersJsonSchema'];
|
|
55
|
+
return this.isPlainObject(parametersJsonSchema)
|
|
56
|
+
? parametersJsonSchema
|
|
57
|
+
: null;
|
|
58
|
+
}
|
|
59
|
+
parseToolArguments(value) {
|
|
60
|
+
if (value === undefined || value === null) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
if (typeof value === 'string') {
|
|
64
|
+
const trimmed = value.trim();
|
|
65
|
+
if (!trimmed) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const parsed = JSON.parse(trimmed);
|
|
69
|
+
return this.isPlainObject(parsed) ? parsed : {};
|
|
70
|
+
}
|
|
71
|
+
return this.isPlainObject(value) ? value : null;
|
|
72
|
+
}
|
|
44
73
|
contentUnionToText(content) {
|
|
45
74
|
if (!content) {
|
|
46
75
|
return '';
|
|
@@ -69,7 +98,7 @@ export class OpenAIContentGenerator {
|
|
|
69
98
|
async generateContent(request, _userPromptId) {
|
|
70
99
|
const contents = toContents(request.contents);
|
|
71
100
|
if (this.hasUnsupportedModalities(contents)) {
|
|
72
|
-
throw new Error('
|
|
101
|
+
throw new Error('The OpenAI provider currently only supports text. To use images or other files, please switch to the Gemini provider.');
|
|
73
102
|
}
|
|
74
103
|
const messages = this.convertContentsToOpenAIMessages(contents);
|
|
75
104
|
const systemText = this.contentUnionToText(request.config?.systemInstruction);
|
|
@@ -92,6 +121,21 @@ export class OpenAIContentGenerator {
|
|
|
92
121
|
if (request.config?.temperature !== undefined) {
|
|
93
122
|
body['temperature'] = request.config.temperature;
|
|
94
123
|
}
|
|
124
|
+
if (request.config?.topP !== undefined) {
|
|
125
|
+
body['top_p'] = request.config.topP;
|
|
126
|
+
}
|
|
127
|
+
if (request.config?.presencePenalty !== undefined) {
|
|
128
|
+
body['presence_penalty'] = request.config.presencePenalty;
|
|
129
|
+
}
|
|
130
|
+
if (request.config?.frequencyPenalty !== undefined) {
|
|
131
|
+
body['frequency_penalty'] = request.config.frequencyPenalty;
|
|
132
|
+
}
|
|
133
|
+
if (request.config?.seed !== undefined) {
|
|
134
|
+
body['seed'] = request.config.seed;
|
|
135
|
+
}
|
|
136
|
+
if (request.config?.stopSequences) {
|
|
137
|
+
body['stop'] = request.config.stopSequences;
|
|
138
|
+
}
|
|
95
139
|
const response = await this.fetchOpenAI('/chat/completions', body, request.config?.abortSignal);
|
|
96
140
|
const data = (await response.json());
|
|
97
141
|
return this.convertOpenAIResponseToGemini(data);
|
|
@@ -99,7 +143,7 @@ export class OpenAIContentGenerator {
|
|
|
99
143
|
async generateContentStream(request, _userPromptId) {
|
|
100
144
|
const contents = toContents(request.contents);
|
|
101
145
|
if (this.hasUnsupportedModalities(contents)) {
|
|
102
|
-
throw new Error('
|
|
146
|
+
throw new Error('The OpenAI provider currently only supports text. To use images or other files, please switch to the Gemini provider.');
|
|
103
147
|
}
|
|
104
148
|
const messages = this.convertContentsToOpenAIMessages(contents);
|
|
105
149
|
const systemText = this.contentUnionToText(request.config?.systemInstruction);
|
|
@@ -121,15 +165,30 @@ export class OpenAIContentGenerator {
|
|
|
121
165
|
body['max_tokens'] = request.config.maxOutputTokens;
|
|
122
166
|
if (request.config?.temperature !== undefined)
|
|
123
167
|
body['temperature'] = request.config.temperature;
|
|
168
|
+
if (request.config?.topP !== undefined)
|
|
169
|
+
body['top_p'] = request.config.topP;
|
|
170
|
+
if (request.config?.presencePenalty !== undefined)
|
|
171
|
+
body['presence_penalty'] = request.config.presencePenalty;
|
|
172
|
+
if (request.config?.frequencyPenalty !== undefined)
|
|
173
|
+
body['frequency_penalty'] = request.config.frequencyPenalty;
|
|
174
|
+
if (request.config?.seed !== undefined)
|
|
175
|
+
body['seed'] = request.config.seed;
|
|
176
|
+
if (request.config?.stopSequences)
|
|
177
|
+
body['stop'] = request.config.stopSequences;
|
|
178
|
+
// Request usage implementation for streaming
|
|
179
|
+
body['stream_options'] = { include_usage: true };
|
|
124
180
|
const response = await this.fetchOpenAI('/chat/completions', body, request.config?.abortSignal);
|
|
125
181
|
if (!response.body) {
|
|
126
182
|
throw new Error('No response body from OpenAI stream');
|
|
127
183
|
}
|
|
128
184
|
const debugMode = this.globalConfig.getDebugMode();
|
|
185
|
+
const toolNameMap = this.toolNameMap;
|
|
186
|
+
const parseToolArguments = (value) => this.parseToolArguments(value);
|
|
129
187
|
return (async function* () {
|
|
130
188
|
const reader = response.body.getReader();
|
|
131
189
|
const decoder = new TextDecoder();
|
|
132
190
|
let buffer = '';
|
|
191
|
+
let hasYieldedAnyText = false;
|
|
133
192
|
// Buffer for streaming tool calls
|
|
134
193
|
const pendingToolCalls = {};
|
|
135
194
|
try {
|
|
@@ -147,20 +206,23 @@ export class OpenAIContentGenerator {
|
|
|
147
206
|
if (trimmed.startsWith('data: ')) {
|
|
148
207
|
try {
|
|
149
208
|
const data = JSON.parse(trimmed.slice(6));
|
|
150
|
-
const
|
|
151
|
-
const
|
|
209
|
+
const choice = data.choices?.[0];
|
|
210
|
+
const delta = choice?.delta;
|
|
211
|
+
const message = choice?.message;
|
|
212
|
+
const finishReason = choice?.finish_reason?.toUpperCase();
|
|
152
213
|
// 1. Handle Text Content
|
|
153
214
|
if (delta?.content) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
},
|
|
215
|
+
hasYieldedAnyText = true;
|
|
216
|
+
const resp = new GenerateContentResponse();
|
|
217
|
+
resp.candidates = [
|
|
218
|
+
{
|
|
219
|
+
content: {
|
|
220
|
+
role: 'model',
|
|
221
|
+
parts: [{ text: delta.content }],
|
|
161
222
|
},
|
|
162
|
-
|
|
163
|
-
|
|
223
|
+
},
|
|
224
|
+
];
|
|
225
|
+
yield resp;
|
|
164
226
|
}
|
|
165
227
|
// 2. Accumulate Tool Calls
|
|
166
228
|
if (delta?.tool_calls) {
|
|
@@ -172,14 +234,21 @@ export class OpenAIContentGenerator {
|
|
|
172
234
|
type: 'function',
|
|
173
235
|
function: {
|
|
174
236
|
name: tc.function?.name || '',
|
|
175
|
-
arguments: tc.function?.arguments
|
|
237
|
+
arguments: typeof tc.function?.arguments === 'string'
|
|
238
|
+
? tc.function.arguments
|
|
239
|
+
: '',
|
|
176
240
|
},
|
|
177
241
|
};
|
|
178
242
|
}
|
|
179
243
|
else {
|
|
244
|
+
if (tc.id && !pendingToolCalls[index].id) {
|
|
245
|
+
pendingToolCalls[index].id = tc.id;
|
|
246
|
+
}
|
|
180
247
|
if (tc.function?.arguments) {
|
|
181
|
-
|
|
182
|
-
|
|
248
|
+
if (typeof tc.function.arguments === 'string') {
|
|
249
|
+
pendingToolCalls[index].function.arguments +=
|
|
250
|
+
tc.function.arguments;
|
|
251
|
+
}
|
|
183
252
|
}
|
|
184
253
|
if (tc.function?.name) {
|
|
185
254
|
pendingToolCalls[index].function.name +=
|
|
@@ -188,10 +257,28 @@ export class OpenAIContentGenerator {
|
|
|
188
257
|
}
|
|
189
258
|
}
|
|
190
259
|
}
|
|
191
|
-
// 3. Handle
|
|
260
|
+
// 3. Handle Usage Metadata (streaming)
|
|
261
|
+
if (data.usage) {
|
|
262
|
+
const usage = data.usage;
|
|
263
|
+
const resp = new GenerateContentResponse();
|
|
264
|
+
resp.usageMetadata = {
|
|
265
|
+
promptTokenCount: usage.prompt_tokens,
|
|
266
|
+
candidatesTokenCount: usage.completion_tokens,
|
|
267
|
+
totalTokenCount: usage.total_tokens,
|
|
268
|
+
};
|
|
269
|
+
// If usage comes in a separate chunk without content, we must yield it
|
|
270
|
+
// Often it's the last chunk.
|
|
271
|
+
yield resp;
|
|
272
|
+
}
|
|
273
|
+
// 4. Handle Finish
|
|
192
274
|
if (finishReason) {
|
|
193
275
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
194
276
|
const parts = [];
|
|
277
|
+
if (!hasYieldedAnyText &&
|
|
278
|
+
typeof message?.content === 'string') {
|
|
279
|
+
parts.push({ text: message.content });
|
|
280
|
+
}
|
|
281
|
+
let hasEmittedToolCall = false;
|
|
195
282
|
// If we have buffered tool calls, finalize them now
|
|
196
283
|
const toolIndices = Object.keys(pendingToolCalls)
|
|
197
284
|
.map(Number)
|
|
@@ -199,13 +286,18 @@ export class OpenAIContentGenerator {
|
|
|
199
286
|
for (const index of toolIndices) {
|
|
200
287
|
const tc = pendingToolCalls[index];
|
|
201
288
|
try {
|
|
202
|
-
const
|
|
289
|
+
const parsedArgs = parseToolArguments(tc.function.arguments);
|
|
290
|
+
const args = parsedArgs ?? {};
|
|
291
|
+
// Unsanitize tool name back to original Gemini format
|
|
292
|
+
const originalName = toolNameMap.get(tc.function.name) || tc.function.name;
|
|
203
293
|
parts.push({
|
|
204
294
|
functionCall: {
|
|
205
|
-
|
|
295
|
+
...(tc.id ? { id: tc.id } : {}),
|
|
296
|
+
name: originalName,
|
|
206
297
|
args,
|
|
207
298
|
},
|
|
208
299
|
});
|
|
300
|
+
hasEmittedToolCall = true;
|
|
209
301
|
}
|
|
210
302
|
catch (e) {
|
|
211
303
|
if (debugMode) {
|
|
@@ -213,17 +305,39 @@ export class OpenAIContentGenerator {
|
|
|
213
305
|
}
|
|
214
306
|
}
|
|
215
307
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
{
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
308
|
+
if (!hasEmittedToolCall && message?.tool_calls) {
|
|
309
|
+
for (const tc of message.tool_calls) {
|
|
310
|
+
try {
|
|
311
|
+
const parsedArgs = parseToolArguments(tc.function.arguments);
|
|
312
|
+
const args = parsedArgs ?? {};
|
|
313
|
+
parts.push({
|
|
314
|
+
functionCall: {
|
|
315
|
+
...(tc.id ? { id: tc.id } : {}),
|
|
316
|
+
name: toolNameMap.get(tc.function.name || '') ||
|
|
317
|
+
tc.function.name ||
|
|
318
|
+
'',
|
|
319
|
+
args,
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
catch (e) {
|
|
324
|
+
if (debugMode) {
|
|
325
|
+
console.error('[OpenAI Provider] Failed to parse message tool arguments:', e);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const resp = new GenerateContentResponse();
|
|
331
|
+
resp.candidates = [
|
|
332
|
+
{
|
|
333
|
+
content: {
|
|
334
|
+
role: 'model',
|
|
335
|
+
parts,
|
|
224
336
|
},
|
|
225
|
-
|
|
226
|
-
|
|
337
|
+
finishReason: finishReason,
|
|
338
|
+
},
|
|
339
|
+
];
|
|
340
|
+
yield resp;
|
|
227
341
|
}
|
|
228
342
|
}
|
|
229
343
|
catch (e) {
|
|
@@ -294,26 +408,55 @@ export class OpenAIContentGenerator {
|
|
|
294
408
|
if (proxy) {
|
|
295
409
|
options.dispatcher = new ProxyAgent(proxy);
|
|
296
410
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
411
|
+
let attempt = 0;
|
|
412
|
+
while (true) {
|
|
413
|
+
attempt++;
|
|
414
|
+
try {
|
|
415
|
+
const response = await fetch(url, options);
|
|
416
|
+
if (response.ok) {
|
|
417
|
+
return response;
|
|
418
|
+
}
|
|
419
|
+
// Check for retryable errors (429, 503)
|
|
420
|
+
if (attempt < 3 &&
|
|
421
|
+
(response.status === 429 || response.status >= 500)) {
|
|
422
|
+
const delay = Math.pow(2, attempt) * 1000 + Math.random() * 500;
|
|
423
|
+
if (this.globalConfig.getDebugMode()) {
|
|
424
|
+
console.warn(`[OpenAI] Request failed with ${response.status}. Retrying in ${Math.round(delay)}ms (attempt ${attempt}/3)`);
|
|
425
|
+
}
|
|
426
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
const text = await response.text().catch(() => '');
|
|
430
|
+
throw new Error(`OpenAI compatible backend error (${response.status}): ${text}`);
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
if (attempt < 3) {
|
|
434
|
+
const delay = Math.pow(2, attempt) * 1000 + Math.random() * 500;
|
|
435
|
+
if (this.globalConfig.getDebugMode()) {
|
|
436
|
+
console.warn(`[OpenAI] Network error. Retrying in ${Math.round(delay)}ms (attempt ${attempt}/3)`, error);
|
|
437
|
+
}
|
|
438
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
throw error;
|
|
442
|
+
}
|
|
301
443
|
}
|
|
302
|
-
return response;
|
|
303
444
|
}
|
|
304
445
|
convertTools(tools) {
|
|
305
446
|
const openAITools = [];
|
|
306
447
|
for (const tool of tools) {
|
|
307
448
|
if (tool.functionDeclarations) {
|
|
308
449
|
for (const fn of tool.functionDeclarations) {
|
|
450
|
+
const parametersFromJsonSchema = this.getParametersJsonSchema(fn);
|
|
309
451
|
openAITools.push({
|
|
310
452
|
type: 'function',
|
|
311
453
|
function: {
|
|
312
|
-
name: fn.name || 'unknown_tool',
|
|
454
|
+
name: this.sanitizeToolName(fn.name || 'unknown_tool'),
|
|
313
455
|
description: fn.description,
|
|
314
|
-
parameters:
|
|
315
|
-
|
|
316
|
-
|
|
456
|
+
parameters: parametersFromJsonSchema ??
|
|
457
|
+
(fn.parameters
|
|
458
|
+
? this.convertSchemaToOpenAI(fn.parameters)
|
|
459
|
+
: undefined),
|
|
317
460
|
},
|
|
318
461
|
});
|
|
319
462
|
}
|
|
@@ -321,6 +464,32 @@ export class OpenAIContentGenerator {
|
|
|
321
464
|
}
|
|
322
465
|
return openAITools;
|
|
323
466
|
}
|
|
467
|
+
/**
|
|
468
|
+
* Sanitizes tool names for OpenAI function calling compatibility.
|
|
469
|
+
* OpenAI spec only allows: ^[a-zA-Z0-9_-]+$ (no dots, must not start with number)
|
|
470
|
+
* This converts names like "ui.click" → "ui_click" at the adapter level,
|
|
471
|
+
* keeping Gemini tool names unchanged in the core codebase.
|
|
472
|
+
* Records the mapping so we can reverse it when parsing model responses.
|
|
473
|
+
*/
|
|
474
|
+
sanitizeToolName(name) {
|
|
475
|
+
// Replace dots and other invalid characters with underscores
|
|
476
|
+
let sanitized = name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
477
|
+
// Ensure name doesn't start with a number
|
|
478
|
+
if (/^[0-9]/.test(sanitized)) {
|
|
479
|
+
sanitized = '_' + sanitized;
|
|
480
|
+
}
|
|
481
|
+
// Record mapping for reverse lookup
|
|
482
|
+
if (sanitized !== name) {
|
|
483
|
+
this.toolNameMap.set(sanitized, name);
|
|
484
|
+
}
|
|
485
|
+
return sanitized;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Reverses tool name sanitization to get original Gemini name.
|
|
489
|
+
*/
|
|
490
|
+
unsanitizeToolName(sanitizedName) {
|
|
491
|
+
return this.toolNameMap.get(sanitizedName) || sanitizedName;
|
|
492
|
+
}
|
|
324
493
|
convertSchemaToOpenAI(schema) {
|
|
325
494
|
// Convert Gemini Schema to JSON Schema format
|
|
326
495
|
// Gemini uses Type enum (OBJECT, STRING, etc.) while JSON Schema uses lowercase strings
|
|
@@ -431,9 +600,10 @@ export class OpenAIContentGenerator {
|
|
|
431
600
|
if (part.functionCall) {
|
|
432
601
|
const name = part.functionCall.name;
|
|
433
602
|
if (name) {
|
|
434
|
-
const id =
|
|
435
|
-
.
|
|
436
|
-
|
|
603
|
+
const id = part.functionCall.id ||
|
|
604
|
+
`call_${name}_${Date.now()}_${Math.random()
|
|
605
|
+
.toString(36)
|
|
606
|
+
.slice(2, 7)}`;
|
|
437
607
|
if (!toolCallIdStack[name])
|
|
438
608
|
toolCallIdStack[name] = [];
|
|
439
609
|
toolCallIdStack[name].push(id);
|
|
@@ -441,7 +611,7 @@ export class OpenAIContentGenerator {
|
|
|
441
611
|
id,
|
|
442
612
|
type: 'function',
|
|
443
613
|
function: {
|
|
444
|
-
name,
|
|
614
|
+
name: this.sanitizeToolName(name),
|
|
445
615
|
arguments: JSON.stringify(part.functionCall.args),
|
|
446
616
|
},
|
|
447
617
|
});
|
|
@@ -459,8 +629,15 @@ export class OpenAIContentGenerator {
|
|
|
459
629
|
}
|
|
460
630
|
// Standard text message
|
|
461
631
|
const textParts = (content.parts || [])
|
|
462
|
-
.
|
|
463
|
-
|
|
632
|
+
.map((p) => {
|
|
633
|
+
if ('thought' in p && p.thought) {
|
|
634
|
+
return `[Thought: ${p.thought}]\n`;
|
|
635
|
+
}
|
|
636
|
+
if ('text' in p && p.text) {
|
|
637
|
+
return p.text;
|
|
638
|
+
}
|
|
639
|
+
return '';
|
|
640
|
+
})
|
|
464
641
|
.join('');
|
|
465
642
|
messages.push({
|
|
466
643
|
role,
|
|
@@ -480,21 +657,29 @@ export class OpenAIContentGenerator {
|
|
|
480
657
|
if (message?.tool_calls) {
|
|
481
658
|
for (const toolCall of message.tool_calls) {
|
|
482
659
|
if (toolCall.type === 'function') {
|
|
483
|
-
let args = {};
|
|
484
660
|
try {
|
|
485
|
-
|
|
661
|
+
const parsedArgs = this.parseToolArguments(toolCall.function.arguments);
|
|
662
|
+
const args = parsedArgs ?? {};
|
|
663
|
+
parts.push({
|
|
664
|
+
functionCall: {
|
|
665
|
+
id: toolCall.id,
|
|
666
|
+
name: this.unsanitizeToolName(toolCall.function.name || ''),
|
|
667
|
+
args,
|
|
668
|
+
},
|
|
669
|
+
});
|
|
486
670
|
}
|
|
487
671
|
catch (e) {
|
|
488
672
|
if (this.globalConfig.getDebugMode()) {
|
|
489
673
|
console.error('[OpenAI Provider] Failed to parse function arguments:', e);
|
|
490
674
|
}
|
|
675
|
+
parts.push({
|
|
676
|
+
functionCall: {
|
|
677
|
+
id: toolCall.id,
|
|
678
|
+
name: this.unsanitizeToolName(toolCall.function.name || ''),
|
|
679
|
+
args: {},
|
|
680
|
+
},
|
|
681
|
+
});
|
|
491
682
|
}
|
|
492
|
-
parts.push({
|
|
493
|
-
functionCall: {
|
|
494
|
-
name: toolCall.function.name || '',
|
|
495
|
-
args,
|
|
496
|
-
},
|
|
497
|
-
});
|
|
498
683
|
}
|
|
499
684
|
}
|
|
500
685
|
}
|
|
@@ -508,6 +693,13 @@ export class OpenAIContentGenerator {
|
|
|
508
693
|
};
|
|
509
694
|
const response = new GenerateContentResponse();
|
|
510
695
|
response.candidates = [candidate];
|
|
696
|
+
if (data.usage) {
|
|
697
|
+
response.usageMetadata = {
|
|
698
|
+
promptTokenCount: data.usage.prompt_tokens,
|
|
699
|
+
candidatesTokenCount: data.usage.completion_tokens,
|
|
700
|
+
totalTokenCount: data.usage.total_tokens,
|
|
701
|
+
};
|
|
702
|
+
}
|
|
511
703
|
return response;
|
|
512
704
|
}
|
|
513
705
|
}
|