@moxxy/plugin-provider-openai 0.1.0 → 0.1.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/dist/compat.d.ts.map +1 -1
- package/dist/compat.js +17 -1
- package/dist/compat.js.map +1 -1
- package/dist/provider.d.ts +13 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +102 -9
- package/dist/provider.js.map +1 -1
- package/dist/translate.d.ts.map +1 -1
- package/dist/translate.js +28 -3
- package/dist/translate.js.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +17 -1
- package/dist/validate.js.map +1 -1
- package/package.json +2 -2
- package/src/compat.test.ts +38 -0
- package/src/compat.ts +19 -1
- package/src/provider.test.ts +104 -0
- package/src/provider.ts +110 -10
- package/src/translate.test.ts +28 -0
- package/src/translate.ts +28 -3
- package/src/validate.test.ts +31 -0
- package/src/validate.ts +18 -1
package/dist/compat.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compat.d.ts","sourceRoot":"","sources":["../src/compat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,eAAe,EAAE,KAAK,sBAAsB,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAIjH;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,kBAAkB,CAO1F;AAED,MAAM,WAAW,8BAA8B;IAC7C;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+FAA+F;IAC/F,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,gGAAgG;IAChG,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,yHAAyH;IACzH,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAChD,oEAAoE;IACpE,QAAQ,CAAC,IAAI,CAAC,EAAE,sBAAsB,CAAC;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,MAAM,GAAG,SAAS,CAAC;IAC5E;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,MAAM,CAAC;CAClE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,8BAA8B,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"compat.d.ts","sourceRoot":"","sources":["../src/compat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,eAAe,EAAE,KAAK,sBAAsB,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAIjH;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,kBAAkB,CAO1F;AAED,MAAM,WAAW,8BAA8B;IAC7C;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+FAA+F;IAC/F,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,gGAAgG;IAChG,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,yHAAyH;IACzH,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAChD,oEAAoE;IACpE,QAAQ,CAAC,IAAI,CAAC,EAAE,sBAAsB,CAAC;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,MAAM,GAAG,SAAS,CAAC;IAC5E;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,MAAM,CAAC;CAClE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,8BAA8B,GAAG,WAAW,CAuC5F"}
|
package/dist/compat.js
CHANGED
|
@@ -31,13 +31,29 @@ export function pickOpenAICompatConfig(config) {
|
|
|
31
31
|
export function defineOpenAICompatProvider(spec) {
|
|
32
32
|
const resolveApiKey = spec.resolveApiKey ?? ((cfg) => cfg.apiKey);
|
|
33
33
|
const resolveBaseURL = spec.resolveBaseURL ?? ((cfg) => cfg.baseURL ?? spec.baseURL);
|
|
34
|
+
// A key-bearing compat vendor ALWAYS pins `baseURL` to its own third-party
|
|
35
|
+
// host. If the resolved apiKey is empty, the OpenAI SDK ctor silently falls
|
|
36
|
+
// back to `process.env.OPENAI_API_KEY` (and OPENAI_BASE_URL is irrelevant
|
|
37
|
+
// because we override baseURL) — which would ship the user's real OpenAI
|
|
38
|
+
// credential to api.x.ai / Google / a runtime-registered vendor on a merely
|
|
39
|
+
// MISCONFIGURED provider (no key set). Refuse an empty resolved key centrally
|
|
40
|
+
// so every authenticating vendor (xai/google/provider-admin entries) is safe
|
|
41
|
+
// without each re-adding its own guard. `validate: false` vendors (local
|
|
42
|
+
// servers that don't authenticate and legitimately use a placeholder key via
|
|
43
|
+
// resolveApiKey) are exempt.
|
|
44
|
+
const requiresKey = spec.validate !== false;
|
|
34
45
|
return defineProvider({
|
|
35
46
|
name: spec.name,
|
|
36
47
|
models: [...spec.models],
|
|
37
48
|
createClient: (config) => {
|
|
38
49
|
const cfg = pickOpenAICompatConfig(config);
|
|
50
|
+
const apiKey = resolveApiKey(cfg);
|
|
51
|
+
if (requiresKey && (typeof apiKey !== 'string' || apiKey.trim().length === 0)) {
|
|
52
|
+
throw new Error(`${spec.name} requires an API key — configure the ${spec.name} provider's apiKey ` +
|
|
53
|
+
`(refusing to fall back to OPENAI_API_KEY against ${spec.name}'s endpoint)`);
|
|
54
|
+
}
|
|
39
55
|
return new OpenAIProvider({
|
|
40
|
-
apiKey
|
|
56
|
+
apiKey,
|
|
41
57
|
name: spec.name,
|
|
42
58
|
baseURL: resolveBaseURL(cfg),
|
|
43
59
|
defaultModel: cfg.defaultModel ?? spec.defaultModel,
|
package/dist/compat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compat.js","sourceRoot":"","sources":["../src/compat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAuE,MAAM,YAAY,CAAC;AACjH,OAAO,EAAE,cAAc,EAA6B,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,WAAW,IAAI,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAevE;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAA+B;IACpE,MAAM,GAAG,GAAG,CAAC,CAAU,EAAsB,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACxF,OAAO;QACL,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC;QAC1B,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC;QAC5B,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC;KACvC,CAAC;AACJ,CAAC;AAwCD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAAoC;IAC7E,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,CAAC,CAAC,GAAuB,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACtF,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,IAAI,CAAC,CAAC,GAAuB,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;IACzG,OAAO,cAAc,CAAC;QACpB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;QACxB,YAAY,EAAE,CAAC,MAAM,EAAE,EAAE;YACvB,MAAM,GAAG,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;YAC3C,
|
|
1
|
+
{"version":3,"file":"compat.js","sourceRoot":"","sources":["../src/compat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAuE,MAAM,YAAY,CAAC;AACjH,OAAO,EAAE,cAAc,EAA6B,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,WAAW,IAAI,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAevE;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAA+B;IACpE,MAAM,GAAG,GAAG,CAAC,CAAU,EAAsB,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACxF,OAAO;QACL,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC;QAC1B,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC;QAC5B,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC;KACvC,CAAC;AACJ,CAAC;AAwCD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAAoC;IAC7E,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,CAAC,CAAC,GAAuB,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACtF,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,IAAI,CAAC,CAAC,GAAuB,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;IACzG,2EAA2E;IAC3E,4EAA4E;IAC5E,0EAA0E;IAC1E,yEAAyE;IACzE,4EAA4E;IAC5E,8EAA8E;IAC9E,6EAA6E;IAC7E,yEAAyE;IACzE,6EAA6E;IAC7E,6BAA6B;IAC7B,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,KAAK,KAAK,CAAC;IAC5C,OAAO,cAAc,CAAC;QACpB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;QACxB,YAAY,EAAE,CAAC,MAAM,EAAE,EAAE;YACvB,MAAM,GAAG,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;YAC3C,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;YAClC,IAAI,WAAW,IAAI,CAAC,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;gBAC9E,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,CAAC,IAAI,wCAAwC,IAAI,CAAC,IAAI,qBAAqB;oBAChF,oDAAoD,IAAI,CAAC,IAAI,cAAc,CAC9E,CAAC;YACJ,CAAC;YACD,OAAO,IAAI,cAAc,CAAC;gBACxB,MAAM;gBACN,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,OAAO,EAAE,cAAc,CAAC,GAAG,CAAC;gBAC5B,YAAY,EAAE,GAAG,CAAC,YAAY,IAAI,IAAI,CAAC,YAAY;gBACnD,MAAM,EAAE,IAAI,CAAC,MAAM;aACW,CAAC,CAAC;QACpC,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,QAAQ,KAAK,KAAK;YACzB,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,uBAAuB,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;QAC9F,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1C,CAAC,CAAC;AACL,CAAC"}
|
package/dist/provider.d.ts
CHANGED
|
@@ -21,6 +21,19 @@ export interface OpenAIProviderConfig {
|
|
|
21
21
|
* instead of missing on the OpenAI list.
|
|
22
22
|
*/
|
|
23
23
|
readonly models?: ReadonlyArray<ModelDescriptor>;
|
|
24
|
+
/**
|
|
25
|
+
* Per-request timeout (ms) for the default client. The OpenAI SDK default is
|
|
26
|
+
* 10 minutes — far too long for an agentic turn, where a stalled
|
|
27
|
+
* OpenAI-compatible backend (a local vLLM/Ollama box that accepts the
|
|
28
|
+
* connection but never streams) would block the whole turn. Default a couple
|
|
29
|
+
* of minutes; hosts can tune. Ignored when `client` is injected.
|
|
30
|
+
*/
|
|
31
|
+
readonly timeoutMs?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Max automatic retries for the default client (SDK default is 2). Ignored
|
|
34
|
+
* when `client` is injected.
|
|
35
|
+
*/
|
|
36
|
+
readonly maxRetries?: number;
|
|
24
37
|
}
|
|
25
38
|
/**
|
|
26
39
|
* Model catalog as of OpenAI's 2026 API surface. The 5.x family supersedes
|
package/dist/provider.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EACf,aAAa,EACb,eAAe,EAEhB,MAAM,YAAY,CAAC;AAIpB,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EACf,aAAa,EACb,eAAe,EAEhB,MAAM,YAAY,CAAC;AAIpB,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IACjD;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAKD;;;;;;;;GAQG;AAMH,eAAO,MAAM,YAAY,EAAE,aAAa,CAAC,eAAe,CAyBvD,CAAC;AAyBF,qBAAa,cAAe,YAAW,WAAW;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;gBAE1B,MAAM,GAAE,oBAAyB;IAetC,MAAM,CAAC,GAAG,EAAE,eAAe,GAAG,aAAa,CAAC,aAAa,CAAC;IA2O3D,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;CA8B1G"}
|
package/dist/provider.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
2
|
import { estimateTextTokens, toFriendlyError } from '@moxxy/sdk';
|
|
3
3
|
import { toOpenAIMessages, toOpenAITools } from './translate.js';
|
|
4
|
+
/** Default per-request timeout for streamed agentic turns (2 minutes). */
|
|
5
|
+
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
4
6
|
/**
|
|
5
7
|
* Model catalog as of OpenAI's 2026 API surface. The 5.x family supersedes
|
|
6
8
|
* the 4o family but the older ones stay listed so existing configs keep
|
|
@@ -37,6 +39,21 @@ export const openAIModels = [
|
|
|
37
39
|
{ id: 'gpt-4o-mini', contextWindow: 128_000, maxOutputTokens: 16_384, supportsTools: true, supportsStreaming: true, supportsImages: true, supportsDocuments: true },
|
|
38
40
|
{ id: 'gpt-4-turbo', contextWindow: 128_000, maxOutputTokens: 4_096, supportsTools: true, supportsStreaming: true, supportsImages: true },
|
|
39
41
|
];
|
|
42
|
+
/**
|
|
43
|
+
* Coarse per-block token charge for non-text content (image/document/audio) in
|
|
44
|
+
* the `countTokens` estimate. The real cost is tile/page-based and unknowable
|
|
45
|
+
* without the provider's tokenizer; a small constant keeps the budget heuristic
|
|
46
|
+
* from either ignoring attachments entirely or ballooning on the base64 blob.
|
|
47
|
+
*/
|
|
48
|
+
const RICH_BLOCK_TOKEN_ESTIMATE = 256;
|
|
49
|
+
function safeStringify(value) {
|
|
50
|
+
try {
|
|
51
|
+
return JSON.stringify(value) ?? '';
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
40
57
|
export class OpenAIProvider {
|
|
41
58
|
name;
|
|
42
59
|
models;
|
|
@@ -50,6 +67,9 @@ export class OpenAIProvider {
|
|
|
50
67
|
new OpenAI({
|
|
51
68
|
apiKey: config.apiKey ?? process.env.OPENAI_API_KEY,
|
|
52
69
|
...(config.baseURL ? { baseURL: config.baseURL } : {}),
|
|
70
|
+
// Bound the worst case of a connection that opens but never streams.
|
|
71
|
+
timeout: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
72
|
+
...(config.maxRetries !== undefined ? { maxRetries: config.maxRetries } : {}),
|
|
53
73
|
});
|
|
54
74
|
this.defaultModel = config.defaultModel ?? 'gpt-5.4-mini';
|
|
55
75
|
}
|
|
@@ -181,7 +201,12 @@ export class OpenAIProvider {
|
|
|
181
201
|
};
|
|
182
202
|
pending.set(idx, entry);
|
|
183
203
|
}
|
|
184
|
-
else if (tcDelta.id) {
|
|
204
|
+
else if (tcDelta.id && !entry.emittedStart) {
|
|
205
|
+
// Adopt a late-arriving id ONLY before tool_use_start fires.
|
|
206
|
+
// Once start is emitted, the id is the dispatcher's correlation
|
|
207
|
+
// key for the matching _delta/_end events; mutating it here (a
|
|
208
|
+
// non-conforming backend that re-echoes a different id mid-call)
|
|
209
|
+
// would orphan the start and drop the tool result silently.
|
|
185
210
|
entry.id = tcDelta.id;
|
|
186
211
|
}
|
|
187
212
|
if (tcDelta.function?.name && !entry.name)
|
|
@@ -214,18 +239,54 @@ export class OpenAIProvider {
|
|
|
214
239
|
}
|
|
215
240
|
// Flush tool_use_end events with parsed arguments.
|
|
216
241
|
for (const entry of pending.values()) {
|
|
242
|
+
// A non-conforming OpenAI-compatible backend (DeepSeek/z.ai/vLLM/Ollama)
|
|
243
|
+
// can stream function.arguments for an index without ever sending
|
|
244
|
+
// function.name. Such an entry never emitted a tool_use_start, so it was
|
|
245
|
+
// being dropped silently — the turn looked like it did nothing. Surface
|
|
246
|
+
// the failure instead of swallowing the call.
|
|
247
|
+
if (!entry.emittedStart) {
|
|
248
|
+
if (entry.name) {
|
|
249
|
+
// Named but never started (no name delta after the entry was created
|
|
250
|
+
// with a name on construction yet emittedStart stayed false) — emit
|
|
251
|
+
// the start now so the call is not lost.
|
|
252
|
+
entry.emittedStart = true;
|
|
253
|
+
yield { type: 'tool_use_start', id: entry.id, name: entry.name };
|
|
254
|
+
}
|
|
255
|
+
else if (entry.argsBuffer) {
|
|
256
|
+
stopReason = 'error';
|
|
257
|
+
yield {
|
|
258
|
+
type: 'error',
|
|
259
|
+
message: `provider streamed tool arguments with no function name for ${entry.id}`,
|
|
260
|
+
retryable: false,
|
|
261
|
+
};
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
// Empty, nameless, never-started: nothing to surface.
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
217
269
|
let parsed = {};
|
|
218
270
|
if (entry.argsBuffer) {
|
|
219
271
|
try {
|
|
220
272
|
parsed = JSON.parse(entry.argsBuffer);
|
|
221
273
|
}
|
|
222
274
|
catch {
|
|
223
|
-
|
|
275
|
+
// A truncated/malformed tool-input stream (content-filter cut, a
|
|
276
|
+
// non-conforming backend) is a real failure, not a valid call with
|
|
277
|
+
// junk args. Surface it as an error and mark the turn `error`
|
|
278
|
+
// (parity with the Anthropic provider) instead of feeding an opaque
|
|
279
|
+
// { _rawPartial } object into the tool as if it were valid input.
|
|
280
|
+
stopReason = 'error';
|
|
281
|
+
yield {
|
|
282
|
+
type: 'error',
|
|
283
|
+
message: `tool_use input JSON was malformed/truncated for ${entry.id}`,
|
|
284
|
+
retryable: false,
|
|
285
|
+
};
|
|
286
|
+
continue;
|
|
224
287
|
}
|
|
225
288
|
}
|
|
226
|
-
|
|
227
|
-
yield { type: 'tool_use_end', id: entry.id, input: parsed };
|
|
228
|
-
}
|
|
289
|
+
yield { type: 'tool_use_end', id: entry.id, input: parsed };
|
|
229
290
|
}
|
|
230
291
|
yield {
|
|
231
292
|
type: 'message_end',
|
|
@@ -241,15 +302,47 @@ export class OpenAIProvider {
|
|
|
241
302
|
}
|
|
242
303
|
async countTokens(req) {
|
|
243
304
|
// OpenAI doesn't expose a free token counter; fall back to a coarse estimate.
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
305
|
+
// Only TEXT content is stringified — for image/document/audio blocks the
|
|
306
|
+
// `data` field is a multi-MB base64 blob, and JSON.stringify-ing it here
|
|
307
|
+
// would materialize the whole payload into a transient string on the hot
|
|
308
|
+
// pre-flight budget path AND wildly over-estimate (4 base64 chars != 1
|
|
309
|
+
// token; binary blocks bill by tiles/pages, not characters). Charge a small
|
|
310
|
+
// fixed constant per rich block instead.
|
|
311
|
+
let textBlob = req.system ?? '';
|
|
312
|
+
let richBlockTokens = 0;
|
|
313
|
+
for (const m of req.messages) {
|
|
314
|
+
for (const c of m.content) {
|
|
315
|
+
if (c.type === 'text') {
|
|
316
|
+
textBlob += c.text;
|
|
317
|
+
}
|
|
318
|
+
else if (c.type === 'tool_result') {
|
|
319
|
+
textBlob += c.content;
|
|
320
|
+
}
|
|
321
|
+
else if (c.type === 'tool_use') {
|
|
322
|
+
// Tool-call inputs are small structured objects; stringify is cheap.
|
|
323
|
+
textBlob += c.name + safeStringify(c.input);
|
|
324
|
+
}
|
|
325
|
+
else if (c.type === 'reasoning') {
|
|
326
|
+
textBlob += c.text;
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
// image / document / audio: opaque base64 — never stringify the data.
|
|
330
|
+
richBlockTokens += RICH_BLOCK_TOKEN_ESTIMATE;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
textBlob += (req.tools ?? []).map((t) => t.name + t.description).join('');
|
|
335
|
+
return estimateTextTokens(textBlob) + richBlockTokens;
|
|
248
336
|
}
|
|
249
337
|
}
|
|
250
338
|
function mapStopReason(s) {
|
|
251
339
|
if (s === 'tool_calls')
|
|
252
340
|
return 'tool_use';
|
|
341
|
+
// Legacy single-function finish reason still emitted by older Azure /
|
|
342
|
+
// OpenAI-compatible deployments for the deprecated function-calling shape;
|
|
343
|
+
// it means a tool call is pending, not a clean completion.
|
|
344
|
+
if (s === 'function_call')
|
|
345
|
+
return 'tool_use';
|
|
253
346
|
if (s === 'length')
|
|
254
347
|
return 'max_tokens';
|
|
255
348
|
if (s === 'stop')
|
package/dist/provider.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAQ5B,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAsCjE,0EAA0E;AAC1E,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAEnC;;;;;;;;GAQG;AACH,6EAA6E;AAC7E,+EAA+E;AAC/E,8EAA8E;AAC9E,+EAA+E;AAC/E,2EAA2E;AAC3E,MAAM,CAAC,MAAM,YAAY,GAAmC;IAC1D,mEAAmE;IACnE,EAAE,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAC3L,EAAE,EAAE,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAE/L,wEAAwE;IACxE,6DAA6D;IAC7D,EAAE,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAC3L,EAAE,EAAE,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAC/L,EAAE,EAAE,EAAE,cAAc,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAC9L,EAAE,EAAE,EAAE,cAAc,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAE9L,4DAA4D;IAC5D,EAAE,EAAE,EAAE,eAAe,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAE/L,kEAAkE;IAClE,EAAE,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IACzL,EAAE,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAEvL,iDAAiD;IACjD,4FAA4F;IAC5F,EAAE,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAClH,EAAE,EAAE,EAAE,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAC9J,EAAE,EAAE,EAAE,aAAa,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE;IACnK,EAAE,EAAE,EAAE,aAAa,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE;CAC1I,CAAC;AASF;;;;;GAKG;AACH,MAAM,yBAAyB,GAAG,GAAG,CAAC;AAEtC,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,OAAO,cAAc;IAChB,IAAI,CAAS;IACb,MAAM,CAAiC;IAC/B,MAAM,CAAS;IACf,YAAY,CAAS;IAEtC,YAAY,SAA+B,EAAE;QAC3C,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,QAAQ,CAAC;QACpC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,YAAY,CAAC;QAC5C,IAAI,CAAC,MAAM;YACT,MAAM,CAAC,MAAM;gBACb,IAAI,MAAM,CAAC;oBACT,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc;oBACnD,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACtD,qEAAqE;oBACrE,OAAO,EAAE,MAAM,CAAC,SAAS,IAAI,kBAAkB;oBAC/C,GAAG,CAAC,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC9E,CAAC,CAAC;QACL,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,YAAY,IAAI,cAAc,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,CAAC,MAAM,CAAC,GAAoB;QAChC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAChD,mEAAmE;QACnE,uEAAuE;QACvE,wEAAwE;QACxE,uEAAuE;QACvE,+BAA+B;QAC/B,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YACf,IAAI,QAAQ,GAAG,CAAC,CAAC;YACjB,OAAO,QAAQ,GAAG,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,QAAQ,CAAE,CAAC,IAAI,KAAK,QAAQ;gBAAE,QAAQ,IAAI,CAAC,CAAC;YAC1F,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACxE,CAAC;QACD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACvF,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC;QAE7C,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC;QAEvC,sEAAsE;QACtE,mEAAmE;QACnE,kEAAkE;QAClE,kEAAkE;QAClE,4CAA4C;QAC5C,MAAM,oBAAoB,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5D,MAAM,aAAa,GAAG,oBAAoB,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,YAAY,CAAC;QAEpF,2EAA2E;QAC3E,4EAA4E;QAC5E,uEAAuE;QACvE,2DAA2D;QAC3D,MAAM,aAAa,GAAG,GAAG,CAAC,SAAS,IAAI,IAAI,IAAI,GAAG,CAAC,SAAS,KAAK,KAAK,CAAC;QACvE,MAAM,eAAe,GAAG,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;QAE7F,sEAAsE;QACtE,iEAAiE;QACjE,4EAA4E;QAC5E,0EAA0E;QAC1E,+DAA+D;QAC/D,MAAM,MAAM,GAAgE;YAC1E,KAAK;YACL,QAAQ,EAAE,QAA2E;YACrF,GAAG,CAAC,KAAK;gBACP,CAAC,CAAC,EAAE,KAAK,EAAE,KAAgE,EAAE;gBAC7E,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1E,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5D,sEAAsE;YACtE,uEAAuE;YACvE,0EAA0E;YAC1E,iEAAiE;YACjE,wEAAwE;YACxE,qEAAqE;YACrE,kEAAkE;YAClE,mEAAmE;YACnE,iBAAiB;YACjB,GAAG,CAAC,aAAa,IAAI,eAAe,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAClF,MAAM,EAAE,IAAI;YACZ,8DAA8D;YAC9D,gEAAgE;YAChE,qEAAqE;YACrE,cAAc,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE;SACxC,CAAC;QAEF,IAAI,MAA8B,CAAC;QACnC,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CACjD,MAAM;YACN,kEAAkE;YAClE,oEAAoE;YACpE,iEAAiE;YACjE,iEAAiE;YACjE,2CAA2C;YAC3C,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAChD,CAAsC,CAAC;QAC1C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,wEAAwE;YACxE,uEAAuE;YACvE,yEAAyE;YACzE,mCAAmC;YACnC,IAAI,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;gBACxB,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;gBAC9D,OAAO;YACT,CAAC;YACD,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,eAAe,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YAC1E,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;QACnD,IAAI,UAAU,GAAe,UAAU,CAAC;QACxC,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,IAAI,cAAc,GAAG,CAAC,CAAC;QAEvB,IAAI,CAAC;YACH,IAAI,KAAK,EAAE,MAAM,GAAG,IAAI,MAA0C,EAAE,CAAC;gBACnE,IAAI,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;oBACxB,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;oBAC9D,OAAO;gBACT,CAAC;gBACD,mEAAmE;gBACnE,sEAAsE;gBACtE,sEAAsE;gBACtE,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;oBACd,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,aAAa,IAAI,OAAO,CAAC;oBAC7C,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,iBAAiB,IAAI,QAAQ,CAAC;oBACnD,mEAAmE;oBACnE,mEAAmE;oBACnE,iDAAiD;oBACjD,cAAc,GAAG,GAAG,CAAC,KAAK,CAAC,qBAAqB,EAAE,aAAa,IAAI,cAAc,CAAC;gBACpF,CAAC;gBACD,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;gBAChC,IAAI,CAAC,MAAM;oBAAE,SAAS;gBACtB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;gBAEjC,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;oBACvD,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;gBACrD,CAAC;gBAED,IAAI,aAAa,EAAE,CAAC;oBAClB,MAAM,SAAS,GAAG,KAAK,CAAC,iBAAiB,IAAI,KAAK,CAAC,SAAS,CAAC;oBAC7D,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,EAAE,CAAC;wBAC/C,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;oBACtD,CAAC;gBACH,CAAC;gBAED,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;oBACrB,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;wBACvC,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC;wBAC/B,IAAI,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;wBAC7B,IAAI,CAAC,KAAK,EAAE,CAAC;4BACX,KAAK,GAAG;gCACN,EAAE,EAAE,OAAO,CAAC,EAAE,IAAI,QAAQ,GAAG,EAAE;gCAC/B,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,IAAI,EAAE;gCAClC,UAAU,EAAE,EAAE;gCACd,YAAY,EAAE,KAAK;6BACpB,CAAC;4BACF,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;wBAC1B,CAAC;6BAAM,IAAI,OAAO,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC;4BAC7C,6DAA6D;4BAC7D,gEAAgE;4BAChE,+DAA+D;4BAC/D,iEAAiE;4BACjE,4DAA4D;4BAC5D,KAAK,CAAC,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC;wBACxB,CAAC;wBACD,IAAI,OAAO,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI;4BAAE,KAAK,CAAC,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;wBAC9E,IAAI,OAAO,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;4BAChE,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC;4BAC1B,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;wBACnE,CAAC;wBACD,IAAI,OAAO,OAAO,CAAC,QAAQ,EAAE,SAAS,KAAK,QAAQ,EAAE,CAAC;4BACpD,KAAK,CAAC,UAAU,IAAI,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;4BAC/C,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;wBAC3F,CAAC;oBACH,CAAC;gBACH,CAAC;gBAED,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;oBACzB,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;gBACnD,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,yEAAyE;YACzE,wEAAwE;YACxE,0EAA0E;YAC1E,IAAI,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;gBACxB,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;gBAC9D,OAAO;YACT,CAAC;YACD,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,eAAe,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YAC1E,OAAO;QACT,CAAC;QAED,mDAAmD;QACnD,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YACrC,yEAAyE;YACzE,kEAAkE;YAClE,yEAAyE;YACzE,wEAAwE;YACxE,8CAA8C;YAC9C,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC;gBACxB,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;oBACf,qEAAqE;oBACrE,oEAAoE;oBACpE,yCAAyC;oBACzC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC;oBAC1B,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;gBACnE,CAAC;qBAAM,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;oBAC5B,UAAU,GAAG,OAAO,CAAC;oBACrB,MAAM;wBACJ,IAAI,EAAE,OAAO;wBACb,OAAO,EAAE,8DAA8D,KAAK,CAAC,EAAE,EAAE;wBACjF,SAAS,EAAE,KAAK;qBACjB,CAAC;oBACF,SAAS;gBACX,CAAC;qBAAM,CAAC;oBACN,sDAAsD;oBACtD,SAAS;gBACX,CAAC;YACH,CAAC;YACD,IAAI,MAAM,GAAY,EAAE,CAAC;YACzB,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;gBACrB,IAAI,CAAC;oBACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBACxC,CAAC;gBAAC,MAAM,CAAC;oBACP,iEAAiE;oBACjE,mEAAmE;oBACnE,8DAA8D;oBAC9D,oEAAoE;oBACpE,kEAAkE;oBAClE,UAAU,GAAG,OAAO,CAAC;oBACrB,MAAM;wBACJ,IAAI,EAAE,OAAO;wBACb,OAAO,EAAE,mDAAmD,KAAK,CAAC,EAAE,EAAE;wBACtE,SAAS,EAAE,KAAK;qBACjB,CAAC;oBACF,SAAS;gBACX,CAAC;YACH,CAAC;YACD,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAC9D,CAAC;QAED,MAAM;YACJ,IAAI,EAAE,aAAa;YACnB,UAAU;YACV,KAAK,EACH,OAAO,GAAG,CAAC,IAAI,QAAQ,GAAG,CAAC;gBACzB,CAAC,CAAC;oBACE,WAAW,EAAE,OAAO;oBACpB,YAAY,EAAE,QAAQ;oBACtB,GAAG,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBACnE;gBACH,CAAC,CAAC,SAAS;SAChB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,GAAqE;QACrF,8EAA8E;QAC9E,yEAAyE;QACzE,yEAAyE;QACzE,yEAAyE;QACzE,uEAAuE;QACvE,4EAA4E;QAC5E,yCAAyC;QACzC,IAAI,QAAQ,GAAG,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC;QAChC,IAAI,eAAe,GAAG,CAAC,CAAC;QACxB,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;YAC7B,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;gBAC1B,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;oBACtB,QAAQ,IAAI,CAAC,CAAC,IAAI,CAAC;gBACrB,CAAC;qBAAM,IAAI,CAAC,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;oBACpC,QAAQ,IAAI,CAAC,CAAC,OAAO,CAAC;gBACxB,CAAC;qBAAM,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;oBACjC,qEAAqE;oBACrE,QAAQ,IAAI,CAAC,CAAC,IAAI,GAAG,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;gBAC9C,CAAC;qBAAM,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;oBAClC,QAAQ,IAAI,CAAC,CAAC,IAAI,CAAC;gBACrB,CAAC;qBAAM,CAAC;oBACN,sEAAsE;oBACtE,eAAe,IAAI,yBAAyB,CAAC;gBAC/C,CAAC;YACH,CAAC;QACH,CAAC;QACD,QAAQ,IAAI,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1E,OAAO,kBAAkB,CAAC,QAAQ,CAAC,GAAG,eAAe,CAAC;IACxD,CAAC;CACF;AA4BD,SAAS,aAAa,CAAC,CAAS;IAC9B,IAAI,CAAC,KAAK,YAAY;QAAE,OAAO,UAAU,CAAC;IAC1C,sEAAsE;IACtE,2EAA2E;IAC3E,2DAA2D;IAC3D,IAAI,CAAC,KAAK,eAAe;QAAE,OAAO,UAAU,CAAC;IAC7C,IAAI,CAAC,KAAK,QAAQ;QAAE,OAAO,YAAY,CAAC;IACxC,IAAI,CAAC,KAAK,MAAM;QAAE,OAAO,UAAU,CAAC;IACpC,IAAI,CAAC,KAAK,gBAAgB;QAAE,OAAO,OAAO,CAAC;IAC3C,OAAO,UAAU,CAAC;AACpB,CAAC"}
|
package/dist/translate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translate.d.ts","sourceRoot":"","sources":["../src/translate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAG3D,MAAM,MAAM,qBAAqB,GAC7B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAErE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;IAC/C,OAAO,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC,qBAAqB,CAAC,GAAG,IAAI,CAAC;IAC/D,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,UAAU,CAAC;QACjB,QAAQ,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;KAC/C,CAAC,CAAC;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,OAAO,CAAC;KACrB,CAAC;CACH;
|
|
1
|
+
{"version":3,"file":"translate.d.ts","sourceRoot":"","sources":["../src/translate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAG3D,MAAM,MAAM,qBAAqB,GAC7B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAErE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;IAC/C,OAAO,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC,qBAAqB,CAAC,GAAG,IAAI,CAAC;IAC/D,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,UAAU,CAAC;QACjB,QAAQ,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;KAC/C,CAAC,CAAC;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,OAAO,CAAC;KACrB,CAAC;CACH;AAiBD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,aAAa,CAAC,eAAe,CAAC,GAAG,iBAAiB,EAAE,CAwF9F;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,aAAa,EAAE,CAS5E"}
|
package/dist/translate.js
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import { zodToJsonSchema } from '@moxxy/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Serialize a tool-call `input` to the JSON string OpenAI expects in
|
|
4
|
+
* `function.arguments`. A circular or otherwise non-serializable object (a
|
|
5
|
+
* hostile/corrupt in-memory replay) would make a bare `JSON.stringify` throw
|
|
6
|
+
* and crash translation of the WHOLE request. Degrade that one argument to
|
|
7
|
+
* `{}` instead of taking down the turn.
|
|
8
|
+
*/
|
|
9
|
+
function safeStringifyArgs(input) {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.stringify(input ?? {}) ?? '{}';
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return '{}';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
2
17
|
export function toOpenAIMessages(messages) {
|
|
3
18
|
const out = [];
|
|
4
19
|
for (const msg of messages) {
|
|
@@ -54,12 +69,22 @@ export function toOpenAIMessages(messages) {
|
|
|
54
69
|
.map((c) => c.text)
|
|
55
70
|
.join('');
|
|
56
71
|
const toolUses = msg.content.filter((c) => c.type === 'tool_use');
|
|
57
|
-
const
|
|
58
|
-
|
|
72
|
+
const hasToolUses = toolUses.length > 0;
|
|
73
|
+
// OpenAI accepts `content: null` on an assistant turn ONLY when it carries
|
|
74
|
+
// `tool_calls`. An assistant message that projected to neither text nor a
|
|
75
|
+
// tool call (e.g. a turn whose only block is a reasoning block, which we
|
|
76
|
+
// don't replay to OpenAI) would otherwise emit `{ content: null }` with no
|
|
77
|
+
// tool_calls — a hard 400. Degrade to an empty-string content so the turn
|
|
78
|
+
// is well-formed instead of crashing the whole request.
|
|
79
|
+
const message = {
|
|
80
|
+
role: 'assistant',
|
|
81
|
+
content: text || (hasToolUses ? null : ''),
|
|
82
|
+
};
|
|
83
|
+
if (hasToolUses) {
|
|
59
84
|
message.tool_calls = toolUses.map((u) => ({
|
|
60
85
|
id: u.id,
|
|
61
86
|
type: 'function',
|
|
62
|
-
function: { name: u.name, arguments:
|
|
87
|
+
function: { name: u.name, arguments: safeStringifyArgs(u.input) },
|
|
63
88
|
}));
|
|
64
89
|
}
|
|
65
90
|
out.push(message);
|
package/dist/translate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translate.js","sourceRoot":"","sources":["../src/translate.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"translate.js","sourceRoot":"","sources":["../src/translate.ts"],"names":[],"mappings":"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"}
|
package/dist/validate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7E,MAAM,WAAW,eAAe;IAC9B,mDAAmD;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK;QACpC,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;SAAE,CAAC;KAC1C,CAAC;IACF;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,gBAAgB,CAAC,
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7E,MAAM,WAAW,eAAe;IAC9B,mDAAmD;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK;QACpC,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;SAAE,CAAC;KAC1C,CAAC;IACF;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAepG"}
|
package/dist/validate.js
CHANGED
|
@@ -15,9 +15,25 @@ export async function validateKey(key, deps = {}) {
|
|
|
15
15
|
return { ok: true };
|
|
16
16
|
}
|
|
17
17
|
catch (err) {
|
|
18
|
-
|
|
18
|
+
// The raw SDK/network message is untrusted, unbounded text — for a vendor
|
|
19
|
+
// baseURL it surfaces that endpoint's error body straight into the setup
|
|
20
|
+
// UI/logs. Strip URLs and cap the length before returning.
|
|
21
|
+
return { ok: false, message: sanitizeErrorMessage(err) };
|
|
19
22
|
}
|
|
20
23
|
}
|
|
24
|
+
const MAX_MESSAGE_LENGTH = 200;
|
|
25
|
+
function sanitizeErrorMessage(err) {
|
|
26
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
27
|
+
const stripped = raw
|
|
28
|
+
.replace(/https?:\/\/\S+/gi, '[url]')
|
|
29
|
+
.replace(/\s+/g, ' ')
|
|
30
|
+
.trim();
|
|
31
|
+
if (!stripped)
|
|
32
|
+
return 'key validation failed';
|
|
33
|
+
return stripped.length > MAX_MESSAGE_LENGTH
|
|
34
|
+
? `${stripped.slice(0, MAX_MESSAGE_LENGTH)}…`
|
|
35
|
+
: stripped;
|
|
36
|
+
}
|
|
21
37
|
function defaultMaker(apiKey, baseURL) {
|
|
22
38
|
return new OpenAI({ apiKey, ...(baseURL ? { baseURL } : {}) });
|
|
23
39
|
}
|
package/dist/validate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAkB5B;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAW,EAAE,OAAwB,EAAE;IACvE,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACvD,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;QACzB,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QAC3B,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAkB5B;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAW,EAAE,OAAwB,EAAE;IACvE,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACvD,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;QACzB,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QAC3B,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,0EAA0E;QAC1E,yEAAyE;QACzE,2DAA2D;QAC3D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,oBAAoB,CAAC,GAAG,CAAC,EAAE,CAAC;IAC3D,CAAC;AACH,CAAC;AAED,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAE/B,SAAS,oBAAoB,CAAC,GAAY;IACxC,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC7D,MAAM,QAAQ,GAAG,GAAG;SACjB,OAAO,CAAC,kBAAkB,EAAE,OAAO,CAAC;SACpC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE,CAAC;IACV,IAAI,CAAC,QAAQ;QAAE,OAAO,uBAAuB,CAAC;IAC9C,OAAO,QAAQ,CAAC,MAAM,GAAG,kBAAkB;QACzC,CAAC,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,kBAAkB,CAAC,GAAG;QAC7C,CAAC,CAAC,QAAQ,CAAC;AACf,CAAC;AAED,SAAS,YAAY,CAAC,MAAc,EAAE,OAAgB;IACpD,OAAO,IAAI,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAE5D,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moxxy/plugin-provider-openai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "OpenAI (and OpenAI-compatible: z.ai, xAI, Google, Ollama/local) LLMProvider plugin for moxxy. Streams Chat Completions with tool_calls; translates moxxy ProviderRequest/Event ↔ the OpenAI wire shape. Register it into an @moxxy/core Session to route turns to OpenAI-compatible models.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"moxxy",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"openai": "^4.80.0",
|
|
48
|
-
"@moxxy/sdk": "0.15.
|
|
48
|
+
"@moxxy/sdk": "0.15.2"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/node": "^22.10.0",
|
package/src/compat.test.ts
CHANGED
|
@@ -101,6 +101,44 @@ describe('defineOpenAICompatProvider', () => {
|
|
|
101
101
|
expect(local.validateKey).toBeUndefined();
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
it('refuses an empty/missing/whitespace key for an authenticating vendor (no silent OPENAI_API_KEY fallback)', () => {
|
|
105
|
+
// The OpenAI SDK ctor falls back to process.env.OPENAI_API_KEY when apiKey
|
|
106
|
+
// is empty; since a compat vendor pins baseURL to its OWN host, that would
|
|
107
|
+
// exfiltrate the user's real OpenAI credential to the vendor's endpoint on a
|
|
108
|
+
// merely misconfigured provider. defineOpenAICompatProvider must refuse it
|
|
109
|
+
// centrally, regardless of the runner's OPENAI_API_KEY env, so each vendor
|
|
110
|
+
// (xai/google/provider-admin) doesn't have to re-add a guard.
|
|
111
|
+
const saved = process.env.OPENAI_API_KEY;
|
|
112
|
+
process.env.OPENAI_API_KEY = 'sk-real-user-openai-key';
|
|
113
|
+
try {
|
|
114
|
+
for (const bad of [{}, { apiKey: undefined }, { apiKey: '' }, { apiKey: ' ' }, { apiKey: 123 }] as Record<
|
|
115
|
+
string,
|
|
116
|
+
unknown
|
|
117
|
+
>[]) {
|
|
118
|
+
expect(() => def.createClient(bad)).toThrow(/api key/i);
|
|
119
|
+
}
|
|
120
|
+
// A real key still constructs fine.
|
|
121
|
+
expect(() => def.createClient({ apiKey: 'vendor-key' })).not.toThrow();
|
|
122
|
+
} finally {
|
|
123
|
+
if (saved === undefined) delete process.env.OPENAI_API_KEY;
|
|
124
|
+
else process.env.OPENAI_API_KEY = saved;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('does NOT require a key when validate:false (local servers use a placeholder)', () => {
|
|
129
|
+
const local = defineOpenAICompatProvider({
|
|
130
|
+
name: 'local',
|
|
131
|
+
baseURL: 'http://localhost:11434/v1',
|
|
132
|
+
defaultModel: 'llama3.3',
|
|
133
|
+
models: TEST_MODELS,
|
|
134
|
+
validate: false,
|
|
135
|
+
// Mirrors the real local provider: no config key → placeholder.
|
|
136
|
+
resolveApiKey: (cfg) => cfg.apiKey ?? 'placeholder',
|
|
137
|
+
});
|
|
138
|
+
expect(() => local.createClient({})).not.toThrow();
|
|
139
|
+
expect(local.createClient({}).name).toBe('local');
|
|
140
|
+
});
|
|
141
|
+
|
|
104
142
|
it('uses resolveApiKey / resolveBaseURL hooks for per-call credential fallback', () => {
|
|
105
143
|
const seen: Array<{ apiKey?: string; baseURL?: string }> = [];
|
|
106
144
|
const def2 = defineOpenAICompatProvider({
|
package/src/compat.ts
CHANGED
|
@@ -84,13 +84,31 @@ export interface DefineOpenAICompatProviderSpec {
|
|
|
84
84
|
export function defineOpenAICompatProvider(spec: DefineOpenAICompatProviderSpec): ProviderDef {
|
|
85
85
|
const resolveApiKey = spec.resolveApiKey ?? ((cfg: OpenAICompatConfig) => cfg.apiKey);
|
|
86
86
|
const resolveBaseURL = spec.resolveBaseURL ?? ((cfg: OpenAICompatConfig) => cfg.baseURL ?? spec.baseURL);
|
|
87
|
+
// A key-bearing compat vendor ALWAYS pins `baseURL` to its own third-party
|
|
88
|
+
// host. If the resolved apiKey is empty, the OpenAI SDK ctor silently falls
|
|
89
|
+
// back to `process.env.OPENAI_API_KEY` (and OPENAI_BASE_URL is irrelevant
|
|
90
|
+
// because we override baseURL) — which would ship the user's real OpenAI
|
|
91
|
+
// credential to api.x.ai / Google / a runtime-registered vendor on a merely
|
|
92
|
+
// MISCONFIGURED provider (no key set). Refuse an empty resolved key centrally
|
|
93
|
+
// so every authenticating vendor (xai/google/provider-admin entries) is safe
|
|
94
|
+
// without each re-adding its own guard. `validate: false` vendors (local
|
|
95
|
+
// servers that don't authenticate and legitimately use a placeholder key via
|
|
96
|
+
// resolveApiKey) are exempt.
|
|
97
|
+
const requiresKey = spec.validate !== false;
|
|
87
98
|
return defineProvider({
|
|
88
99
|
name: spec.name,
|
|
89
100
|
models: [...spec.models],
|
|
90
101
|
createClient: (config) => {
|
|
91
102
|
const cfg = pickOpenAICompatConfig(config);
|
|
103
|
+
const apiKey = resolveApiKey(cfg);
|
|
104
|
+
if (requiresKey && (typeof apiKey !== 'string' || apiKey.trim().length === 0)) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`${spec.name} requires an API key — configure the ${spec.name} provider's apiKey ` +
|
|
107
|
+
`(refusing to fall back to OPENAI_API_KEY against ${spec.name}'s endpoint)`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
92
110
|
return new OpenAIProvider({
|
|
93
|
-
apiKey
|
|
111
|
+
apiKey,
|
|
94
112
|
name: spec.name,
|
|
95
113
|
baseURL: resolveBaseURL(cfg),
|
|
96
114
|
defaultModel: cfg.defaultModel ?? spec.defaultModel,
|
package/src/provider.test.ts
CHANGED
|
@@ -73,11 +73,82 @@ describe('OpenAIProvider.stream', () => {
|
|
|
73
73
|
expect(last).toMatchObject({ type: 'message_end', stopReason: 'tool_use' });
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
+
it('surfaces an error (not a silent drop) when a backend streams tool arguments with no function name', async () => {
|
|
77
|
+
// Non-conforming OpenAI-compatible backend: argument deltas for an index
|
|
78
|
+
// but function.name is never sent. The call must not be swallowed.
|
|
79
|
+
const fake = fakeOpenAI([
|
|
80
|
+
{ choices: [{ index: 0, delta: { tool_calls: [{ index: 0, function: { arguments: '{"path":"/x"}' } }] } }] },
|
|
81
|
+
{ choices: [{ index: 0, delta: {}, finish_reason: 'tool_calls' }] },
|
|
82
|
+
]);
|
|
83
|
+
const p = new OpenAIProvider({ client: fake as never });
|
|
84
|
+
const events = [];
|
|
85
|
+
for await (const e of p.stream({ model: 'gpt-4o-mini', messages: [] })) events.push(e);
|
|
86
|
+
expect(events.some((e) => e.type === 'tool_use_end')).toBe(false);
|
|
87
|
+
const err = events.find((e) => e.type === 'error');
|
|
88
|
+
expect(err).toMatchObject({ retryable: false });
|
|
89
|
+
expect((err as { message: string }).message).toMatch(/no function name/i);
|
|
90
|
+
expect(events[events.length - 1]).toMatchObject({ type: 'message_end', stopReason: 'error' });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('surfaces an error (not a malformed _rawPartial input) when tool arguments are truncated/invalid JSON', async () => {
|
|
94
|
+
const fake = fakeOpenAI([
|
|
95
|
+
{ choices: [{ index: 0, delta: { tool_calls: [{ index: 0, id: 'call_1', function: { name: 'Read', arguments: '{"path":' } }] } }] },
|
|
96
|
+
{ choices: [{ index: 0, delta: {}, finish_reason: 'tool_calls' }] },
|
|
97
|
+
]);
|
|
98
|
+
const p = new OpenAIProvider({ client: fake as never });
|
|
99
|
+
const events = [];
|
|
100
|
+
for await (const e of p.stream({ model: 'gpt-4o-mini', messages: [] })) events.push(e);
|
|
101
|
+
// No tool_use_end carrying a salvaged junk object.
|
|
102
|
+
expect(events.some((e) => e.type === 'tool_use_end')).toBe(false);
|
|
103
|
+
const err = events.find((e) => e.type === 'error');
|
|
104
|
+
expect(err).toMatchObject({ retryable: false });
|
|
105
|
+
expect((err as { message: string }).message).toMatch(/malformed|truncated/i);
|
|
106
|
+
expect(events[events.length - 1]).toMatchObject({ type: 'message_end', stopReason: 'error' });
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('recovers a named tool call whose start was never emitted (name only, no args delta)', async () => {
|
|
110
|
+
// An entry created with a name but where the start somehow was not flushed
|
|
111
|
+
// mid-stream is still surfaced at flush time rather than dropped.
|
|
112
|
+
const fake = fakeOpenAI([
|
|
113
|
+
{ choices: [{ index: 0, delta: { tool_calls: [{ index: 0, id: 'call_9', function: { name: 'Glob', arguments: '{"pattern":"*.ts"}' } }] } }] },
|
|
114
|
+
{ choices: [{ index: 0, delta: {}, finish_reason: 'tool_calls' }] },
|
|
115
|
+
]);
|
|
116
|
+
const p = new OpenAIProvider({ client: fake as never });
|
|
117
|
+
const events = [];
|
|
118
|
+
for await (const e of p.stream({ model: 'gpt-4o-mini', messages: [] })) events.push(e);
|
|
119
|
+
expect(events.find((e) => e.type === 'tool_use_start')).toMatchObject({ id: 'call_9', name: 'Glob' });
|
|
120
|
+
expect(events.find((e) => e.type === 'tool_use_end')).toMatchObject({ id: 'call_9', input: { pattern: '*.ts' } });
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('does not mutate a tool-call id after tool_use_start (a re-echoed id must not orphan the start)', async () => {
|
|
124
|
+
// A non-conforming backend re-sends a DIFFERENT id on a later delta for the
|
|
125
|
+
// same index. Once tool_use_start fired with the first id, that id is the
|
|
126
|
+
// dispatcher's correlation key — _delta/_end must keep using it, not the
|
|
127
|
+
// late one, or the tool result is dropped silently.
|
|
128
|
+
const fake = fakeOpenAI([
|
|
129
|
+
{ choices: [{ index: 0, delta: { tool_calls: [{ index: 0, id: 'call_A', function: { name: 'Read', arguments: '{"p":' } }] } }] },
|
|
130
|
+
{ choices: [{ index: 0, delta: { tool_calls: [{ index: 0, id: 'call_B', function: { arguments: '1}' } }] } }] },
|
|
131
|
+
{ choices: [{ index: 0, delta: {}, finish_reason: 'tool_calls' }] },
|
|
132
|
+
]);
|
|
133
|
+
const p = new OpenAIProvider({ client: fake as never });
|
|
134
|
+
const events = [];
|
|
135
|
+
for await (const e of p.stream({ model: 'gpt-4o-mini', messages: [] })) events.push(e);
|
|
136
|
+
const start = events.find((e) => e.type === 'tool_use_start') as { id: string };
|
|
137
|
+
const end = events.find((e) => e.type === 'tool_use_end') as { id: string; input: unknown };
|
|
138
|
+
const deltas = events.filter((e) => e.type === 'tool_use_delta') as Array<{ id: string }>;
|
|
139
|
+
expect(start.id).toBe('call_A');
|
|
140
|
+
expect(end.id).toBe('call_A');
|
|
141
|
+
// Every delta correlates to the started id, not the re-echoed one.
|
|
142
|
+
for (const d of deltas) expect(d.id).toBe('call_A');
|
|
143
|
+
expect(end.input).toEqual({ p: 1 });
|
|
144
|
+
});
|
|
145
|
+
|
|
76
146
|
it('maps OpenAI finish_reason values to moxxy stop reasons', async () => {
|
|
77
147
|
const cases: Array<[string, string]> = [
|
|
78
148
|
['stop', 'end_turn'],
|
|
79
149
|
['length', 'max_tokens'],
|
|
80
150
|
['tool_calls', 'tool_use'],
|
|
151
|
+
['function_call', 'tool_use'],
|
|
81
152
|
['content_filter', 'error'],
|
|
82
153
|
];
|
|
83
154
|
for (const [reason, expected] of cases) {
|
|
@@ -303,6 +374,39 @@ describe('OpenAIProvider.stream', () => {
|
|
|
303
374
|
expect(n).toBeGreaterThan(0);
|
|
304
375
|
});
|
|
305
376
|
|
|
377
|
+
it('countTokens does not stringify or over-count multi-MB base64 image/document blocks', async () => {
|
|
378
|
+
const p = new OpenAIProvider({ client: fakeOpenAI([]) as never });
|
|
379
|
+
// ~3MB base64 blob — JSON.stringify-ing this and counting its length would
|
|
380
|
+
// produce ~750k tokens. With the fixed per-block charge it stays bounded.
|
|
381
|
+
const bigData = 'A'.repeat(3_000_000);
|
|
382
|
+
const n = await p.countTokens({
|
|
383
|
+
model: 'gpt-4o',
|
|
384
|
+
messages: [
|
|
385
|
+
{
|
|
386
|
+
role: 'user',
|
|
387
|
+
content: [
|
|
388
|
+
{ type: 'text', text: 'describe this' },
|
|
389
|
+
{ type: 'image', mediaType: 'image/png', data: bigData },
|
|
390
|
+
{ type: 'document', mediaType: 'application/pdf', data: bigData },
|
|
391
|
+
],
|
|
392
|
+
},
|
|
393
|
+
],
|
|
394
|
+
});
|
|
395
|
+
// Must reflect the text + a small constant per rich block, NOT the blob size.
|
|
396
|
+
expect(n).toBeGreaterThan(0);
|
|
397
|
+
expect(n).toBeLessThan(10_000);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it('configures an explicit timeout on the default client (no SDK 10-minute default)', () => {
|
|
401
|
+
// No injected client → real OpenAI ctor; assert the timeout is bounded.
|
|
402
|
+
const p = new OpenAIProvider({ apiKey: 'sk-test', timeoutMs: 5_000 });
|
|
403
|
+
const client = (p as unknown as { client: { timeout?: number } }).client;
|
|
404
|
+
expect(client.timeout).toBe(5_000);
|
|
405
|
+
const dflt = new OpenAIProvider({ apiKey: 'sk-test' });
|
|
406
|
+
const dfltClient = (dflt as unknown as { client: { timeout?: number } }).client;
|
|
407
|
+
expect(dfltClient.timeout).toBeLessThan(600_000);
|
|
408
|
+
});
|
|
409
|
+
|
|
306
410
|
it('delivers hook-injected req.system as a system message after the leading system prompt', async () => {
|
|
307
411
|
let captured: { messages?: Array<{ role: string; content?: unknown }> } | undefined;
|
|
308
412
|
const fake = {
|
package/src/provider.ts
CHANGED
|
@@ -30,8 +30,24 @@ export interface OpenAIProviderConfig {
|
|
|
30
30
|
* instead of missing on the OpenAI list.
|
|
31
31
|
*/
|
|
32
32
|
readonly models?: ReadonlyArray<ModelDescriptor>;
|
|
33
|
+
/**
|
|
34
|
+
* Per-request timeout (ms) for the default client. The OpenAI SDK default is
|
|
35
|
+
* 10 minutes — far too long for an agentic turn, where a stalled
|
|
36
|
+
* OpenAI-compatible backend (a local vLLM/Ollama box that accepts the
|
|
37
|
+
* connection but never streams) would block the whole turn. Default a couple
|
|
38
|
+
* of minutes; hosts can tune. Ignored when `client` is injected.
|
|
39
|
+
*/
|
|
40
|
+
readonly timeoutMs?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Max automatic retries for the default client (SDK default is 2). Ignored
|
|
43
|
+
* when `client` is injected.
|
|
44
|
+
*/
|
|
45
|
+
readonly maxRetries?: number;
|
|
33
46
|
}
|
|
34
47
|
|
|
48
|
+
/** Default per-request timeout for streamed agentic turns (2 minutes). */
|
|
49
|
+
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
50
|
+
|
|
35
51
|
/**
|
|
36
52
|
* Model catalog as of OpenAI's 2026 API surface. The 5.x family supersedes
|
|
37
53
|
* the 4o family but the older ones stay listed so existing configs keep
|
|
@@ -80,6 +96,22 @@ interface PendingToolCall {
|
|
|
80
96
|
emittedStart: boolean;
|
|
81
97
|
}
|
|
82
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Coarse per-block token charge for non-text content (image/document/audio) in
|
|
101
|
+
* the `countTokens` estimate. The real cost is tile/page-based and unknowable
|
|
102
|
+
* without the provider's tokenizer; a small constant keeps the budget heuristic
|
|
103
|
+
* from either ignoring attachments entirely or ballooning on the base64 blob.
|
|
104
|
+
*/
|
|
105
|
+
const RICH_BLOCK_TOKEN_ESTIMATE = 256;
|
|
106
|
+
|
|
107
|
+
function safeStringify(value: unknown): string {
|
|
108
|
+
try {
|
|
109
|
+
return JSON.stringify(value) ?? '';
|
|
110
|
+
} catch {
|
|
111
|
+
return '';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
83
115
|
export class OpenAIProvider implements LLMProvider {
|
|
84
116
|
readonly name: string;
|
|
85
117
|
readonly models: ReadonlyArray<ModelDescriptor>;
|
|
@@ -94,6 +126,9 @@ export class OpenAIProvider implements LLMProvider {
|
|
|
94
126
|
new OpenAI({
|
|
95
127
|
apiKey: config.apiKey ?? process.env.OPENAI_API_KEY,
|
|
96
128
|
...(config.baseURL ? { baseURL: config.baseURL } : {}),
|
|
129
|
+
// Bound the worst case of a connection that opens but never streams.
|
|
130
|
+
timeout: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
131
|
+
...(config.maxRetries !== undefined ? { maxRetries: config.maxRetries } : {}),
|
|
97
132
|
});
|
|
98
133
|
this.defaultModel = config.defaultModel ?? 'gpt-5.4-mini';
|
|
99
134
|
}
|
|
@@ -234,7 +269,12 @@ export class OpenAIProvider implements LLMProvider {
|
|
|
234
269
|
emittedStart: false,
|
|
235
270
|
};
|
|
236
271
|
pending.set(idx, entry);
|
|
237
|
-
} else if (tcDelta.id) {
|
|
272
|
+
} else if (tcDelta.id && !entry.emittedStart) {
|
|
273
|
+
// Adopt a late-arriving id ONLY before tool_use_start fires.
|
|
274
|
+
// Once start is emitted, the id is the dispatcher's correlation
|
|
275
|
+
// key for the matching _delta/_end events; mutating it here (a
|
|
276
|
+
// non-conforming backend that re-echoes a different id mid-call)
|
|
277
|
+
// would orphan the start and drop the tool result silently.
|
|
238
278
|
entry.id = tcDelta.id;
|
|
239
279
|
}
|
|
240
280
|
if (tcDelta.function?.name && !entry.name) entry.name = tcDelta.function.name;
|
|
@@ -267,17 +307,51 @@ export class OpenAIProvider implements LLMProvider {
|
|
|
267
307
|
|
|
268
308
|
// Flush tool_use_end events with parsed arguments.
|
|
269
309
|
for (const entry of pending.values()) {
|
|
310
|
+
// A non-conforming OpenAI-compatible backend (DeepSeek/z.ai/vLLM/Ollama)
|
|
311
|
+
// can stream function.arguments for an index without ever sending
|
|
312
|
+
// function.name. Such an entry never emitted a tool_use_start, so it was
|
|
313
|
+
// being dropped silently — the turn looked like it did nothing. Surface
|
|
314
|
+
// the failure instead of swallowing the call.
|
|
315
|
+
if (!entry.emittedStart) {
|
|
316
|
+
if (entry.name) {
|
|
317
|
+
// Named but never started (no name delta after the entry was created
|
|
318
|
+
// with a name on construction yet emittedStart stayed false) — emit
|
|
319
|
+
// the start now so the call is not lost.
|
|
320
|
+
entry.emittedStart = true;
|
|
321
|
+
yield { type: 'tool_use_start', id: entry.id, name: entry.name };
|
|
322
|
+
} else if (entry.argsBuffer) {
|
|
323
|
+
stopReason = 'error';
|
|
324
|
+
yield {
|
|
325
|
+
type: 'error',
|
|
326
|
+
message: `provider streamed tool arguments with no function name for ${entry.id}`,
|
|
327
|
+
retryable: false,
|
|
328
|
+
};
|
|
329
|
+
continue;
|
|
330
|
+
} else {
|
|
331
|
+
// Empty, nameless, never-started: nothing to surface.
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
270
335
|
let parsed: unknown = {};
|
|
271
336
|
if (entry.argsBuffer) {
|
|
272
337
|
try {
|
|
273
338
|
parsed = JSON.parse(entry.argsBuffer);
|
|
274
339
|
} catch {
|
|
275
|
-
|
|
340
|
+
// A truncated/malformed tool-input stream (content-filter cut, a
|
|
341
|
+
// non-conforming backend) is a real failure, not a valid call with
|
|
342
|
+
// junk args. Surface it as an error and mark the turn `error`
|
|
343
|
+
// (parity with the Anthropic provider) instead of feeding an opaque
|
|
344
|
+
// { _rawPartial } object into the tool as if it were valid input.
|
|
345
|
+
stopReason = 'error';
|
|
346
|
+
yield {
|
|
347
|
+
type: 'error',
|
|
348
|
+
message: `tool_use input JSON was malformed/truncated for ${entry.id}`,
|
|
349
|
+
retryable: false,
|
|
350
|
+
};
|
|
351
|
+
continue;
|
|
276
352
|
}
|
|
277
353
|
}
|
|
278
|
-
|
|
279
|
-
yield { type: 'tool_use_end', id: entry.id, input: parsed };
|
|
280
|
-
}
|
|
354
|
+
yield { type: 'tool_use_end', id: entry.id, input: parsed };
|
|
281
355
|
}
|
|
282
356
|
|
|
283
357
|
yield {
|
|
@@ -296,11 +370,33 @@ export class OpenAIProvider implements LLMProvider {
|
|
|
296
370
|
|
|
297
371
|
async countTokens(req: Pick<ProviderRequest, 'model' | 'messages' | 'system' | 'tools'>): Promise<number> {
|
|
298
372
|
// OpenAI doesn't expose a free token counter; fall back to a coarse estimate.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
373
|
+
// Only TEXT content is stringified — for image/document/audio blocks the
|
|
374
|
+
// `data` field is a multi-MB base64 blob, and JSON.stringify-ing it here
|
|
375
|
+
// would materialize the whole payload into a transient string on the hot
|
|
376
|
+
// pre-flight budget path AND wildly over-estimate (4 base64 chars != 1
|
|
377
|
+
// token; binary blocks bill by tiles/pages, not characters). Charge a small
|
|
378
|
+
// fixed constant per rich block instead.
|
|
379
|
+
let textBlob = req.system ?? '';
|
|
380
|
+
let richBlockTokens = 0;
|
|
381
|
+
for (const m of req.messages) {
|
|
382
|
+
for (const c of m.content) {
|
|
383
|
+
if (c.type === 'text') {
|
|
384
|
+
textBlob += c.text;
|
|
385
|
+
} else if (c.type === 'tool_result') {
|
|
386
|
+
textBlob += c.content;
|
|
387
|
+
} else if (c.type === 'tool_use') {
|
|
388
|
+
// Tool-call inputs are small structured objects; stringify is cheap.
|
|
389
|
+
textBlob += c.name + safeStringify(c.input);
|
|
390
|
+
} else if (c.type === 'reasoning') {
|
|
391
|
+
textBlob += c.text;
|
|
392
|
+
} else {
|
|
393
|
+
// image / document / audio: opaque base64 — never stringify the data.
|
|
394
|
+
richBlockTokens += RICH_BLOCK_TOKEN_ESTIMATE;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
textBlob += (req.tools ?? []).map((t) => t.name + t.description).join('');
|
|
399
|
+
return estimateTextTokens(textBlob) + richBlockTokens;
|
|
304
400
|
}
|
|
305
401
|
}
|
|
306
402
|
|
|
@@ -332,6 +428,10 @@ interface OpenAIStreamChunk {
|
|
|
332
428
|
|
|
333
429
|
function mapStopReason(s: string): StopReason {
|
|
334
430
|
if (s === 'tool_calls') return 'tool_use';
|
|
431
|
+
// Legacy single-function finish reason still emitted by older Azure /
|
|
432
|
+
// OpenAI-compatible deployments for the deprecated function-calling shape;
|
|
433
|
+
// it means a tool call is pending, not a clean completion.
|
|
434
|
+
if (s === 'function_call') return 'tool_use';
|
|
335
435
|
if (s === 'length') return 'max_tokens';
|
|
336
436
|
if (s === 'stop') return 'end_turn';
|
|
337
437
|
if (s === 'content_filter') return 'error';
|
package/src/translate.test.ts
CHANGED
|
@@ -91,6 +91,34 @@ describe('toOpenAIMessages', () => {
|
|
|
91
91
|
expect(out[0].content).toBe('hello\nworld');
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
+
it('emits an empty-string (not null) content for an assistant turn with neither text nor tool_calls', () => {
|
|
95
|
+
// A turn whose only block is reasoning (not replayed to OpenAI) would
|
|
96
|
+
// otherwise produce { content: null } with no tool_calls — a hard OpenAI
|
|
97
|
+
// 400. Degrade to '' so the request stays well-formed.
|
|
98
|
+
const out = toOpenAIMessages([
|
|
99
|
+
{ role: 'assistant', content: [{ type: 'reasoning', text: 'thinking…' }] },
|
|
100
|
+
]);
|
|
101
|
+
expect(out[0]).toEqual({ role: 'assistant', content: '' });
|
|
102
|
+
expect(out[0].tool_calls).toBeUndefined();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('keeps content:null only when tool_calls are present', () => {
|
|
106
|
+
const out = toOpenAIMessages([
|
|
107
|
+
{ role: 'assistant', content: [{ type: 'tool_use', id: 'c1', name: 'Read', input: { path: '/a' } }] },
|
|
108
|
+
]);
|
|
109
|
+
expect(out[0].content).toBeNull();
|
|
110
|
+
expect(out[0].tool_calls).toHaveLength(1);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('does not crash on a circular/non-serializable tool_use input — degrades that arg to {}', () => {
|
|
114
|
+
const circular: Record<string, unknown> = { a: 1 };
|
|
115
|
+
circular.self = circular;
|
|
116
|
+
const out = toOpenAIMessages([
|
|
117
|
+
{ role: 'assistant', content: [{ type: 'tool_use', id: 'c1', name: 'Bad', input: circular }] },
|
|
118
|
+
]);
|
|
119
|
+
expect(out[0].tool_calls?.[0].function.arguments).toBe('{}');
|
|
120
|
+
});
|
|
121
|
+
|
|
94
122
|
it('handles assistant with both text and tool_calls', () => {
|
|
95
123
|
const out = toOpenAIMessages([
|
|
96
124
|
{
|
package/src/translate.ts
CHANGED
|
@@ -27,6 +27,21 @@ export interface OpenAIToolDef {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Serialize a tool-call `input` to the JSON string OpenAI expects in
|
|
32
|
+
* `function.arguments`. A circular or otherwise non-serializable object (a
|
|
33
|
+
* hostile/corrupt in-memory replay) would make a bare `JSON.stringify` throw
|
|
34
|
+
* and crash translation of the WHOLE request. Degrade that one argument to
|
|
35
|
+
* `{}` instead of taking down the turn.
|
|
36
|
+
*/
|
|
37
|
+
function safeStringifyArgs(input: unknown): string {
|
|
38
|
+
try {
|
|
39
|
+
return JSON.stringify(input ?? {}) ?? '{}';
|
|
40
|
+
} catch {
|
|
41
|
+
return '{}';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
30
45
|
export function toOpenAIMessages(messages: ReadonlyArray<ProviderMessage>): OpenAIChatMessage[] {
|
|
31
46
|
const out: OpenAIChatMessage[] = [];
|
|
32
47
|
for (const msg of messages) {
|
|
@@ -81,12 +96,22 @@ export function toOpenAIMessages(messages: ReadonlyArray<ProviderMessage>): Open
|
|
|
81
96
|
(c): c is { type: 'tool_use'; id: string; name: string; input: unknown } =>
|
|
82
97
|
c.type === 'tool_use',
|
|
83
98
|
);
|
|
84
|
-
const
|
|
85
|
-
|
|
99
|
+
const hasToolUses = toolUses.length > 0;
|
|
100
|
+
// OpenAI accepts `content: null` on an assistant turn ONLY when it carries
|
|
101
|
+
// `tool_calls`. An assistant message that projected to neither text nor a
|
|
102
|
+
// tool call (e.g. a turn whose only block is a reasoning block, which we
|
|
103
|
+
// don't replay to OpenAI) would otherwise emit `{ content: null }` with no
|
|
104
|
+
// tool_calls — a hard 400. Degrade to an empty-string content so the turn
|
|
105
|
+
// is well-formed instead of crashing the whole request.
|
|
106
|
+
const message: OpenAIChatMessage = {
|
|
107
|
+
role: 'assistant',
|
|
108
|
+
content: text || (hasToolUses ? null : ''),
|
|
109
|
+
};
|
|
110
|
+
if (hasToolUses) {
|
|
86
111
|
message.tool_calls = toolUses.map((u) => ({
|
|
87
112
|
id: u.id,
|
|
88
113
|
type: 'function' as const,
|
|
89
|
-
function: { name: u.name, arguments:
|
|
114
|
+
function: { name: u.name, arguments: safeStringifyArgs(u.input) },
|
|
90
115
|
}));
|
|
91
116
|
}
|
|
92
117
|
out.push(message);
|
package/src/validate.test.ts
CHANGED
|
@@ -29,4 +29,35 @@ describe('openai validateKey', () => {
|
|
|
29
29
|
expect(res.ok).toBe(false);
|
|
30
30
|
if (!res.ok) expect(res.message).toContain('Incorrect API key');
|
|
31
31
|
});
|
|
32
|
+
|
|
33
|
+
it('strips URLs and caps length of the surfaced error message', async () => {
|
|
34
|
+
const long = 'x'.repeat(500);
|
|
35
|
+
const make = () => ({
|
|
36
|
+
models: {
|
|
37
|
+
list: async () => {
|
|
38
|
+
throw new Error(`failed talking to https://evil.example/v1/models?key=leak ${long}`);
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
const res = await validateKey('sk-bad-but-long-enough', { client: make });
|
|
43
|
+
expect(res.ok).toBe(false);
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
expect(res.message).not.toContain('https://');
|
|
46
|
+
expect(res.message).toContain('[url]');
|
|
47
|
+
expect(res.message.length).toBeLessThanOrEqual(201);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('does not crash on a non-Error thrown value and never returns an empty message', async () => {
|
|
52
|
+
const make = () => ({
|
|
53
|
+
models: {
|
|
54
|
+
list: async () => {
|
|
55
|
+
throw ' ';
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
const res = await validateKey('sk-bad-but-long-enough', { client: make });
|
|
60
|
+
expect(res.ok).toBe(false);
|
|
61
|
+
if (!res.ok) expect(res.message.length).toBeGreaterThan(0);
|
|
62
|
+
});
|
|
32
63
|
});
|
package/src/validate.ts
CHANGED
|
@@ -31,10 +31,27 @@ export async function validateKey(key: string, deps: ValidateKeyDeps = {}): Prom
|
|
|
31
31
|
await client.models.list();
|
|
32
32
|
return { ok: true };
|
|
33
33
|
} catch (err) {
|
|
34
|
-
|
|
34
|
+
// The raw SDK/network message is untrusted, unbounded text — for a vendor
|
|
35
|
+
// baseURL it surfaces that endpoint's error body straight into the setup
|
|
36
|
+
// UI/logs. Strip URLs and cap the length before returning.
|
|
37
|
+
return { ok: false, message: sanitizeErrorMessage(err) };
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
|
|
41
|
+
const MAX_MESSAGE_LENGTH = 200;
|
|
42
|
+
|
|
43
|
+
function sanitizeErrorMessage(err: unknown): string {
|
|
44
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
45
|
+
const stripped = raw
|
|
46
|
+
.replace(/https?:\/\/\S+/gi, '[url]')
|
|
47
|
+
.replace(/\s+/g, ' ')
|
|
48
|
+
.trim();
|
|
49
|
+
if (!stripped) return 'key validation failed';
|
|
50
|
+
return stripped.length > MAX_MESSAGE_LENGTH
|
|
51
|
+
? `${stripped.slice(0, MAX_MESSAGE_LENGTH)}…`
|
|
52
|
+
: stripped;
|
|
53
|
+
}
|
|
54
|
+
|
|
38
55
|
function defaultMaker(apiKey: string, baseURL?: string): { models: { list: () => Promise<unknown> } } {
|
|
39
56
|
return new OpenAI({ apiKey, ...(baseURL ? { baseURL } : {}) }) as unknown as {
|
|
40
57
|
models: { list: () => Promise<unknown> };
|