@moxxy/plugin-provider-openai 0.1.0 → 0.1.1

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.
@@ -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,CAqB5F"}
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: resolveApiKey(cfg),
56
+ apiKey,
41
57
  name: spec.name,
42
58
  baseURL: resolveBaseURL(cfg),
43
59
  defaultModel: cfg.defaultModel ?? spec.defaultModel,
@@ -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,OAAO,IAAI,cAAc,CAAC;gBACxB,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC;gBAC1B,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"}
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"}
@@ -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
@@ -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;CAClD;AAED;;;;;;;;GAQG;AAMH,eAAO,MAAM,YAAY,EAAE,aAAa,CAAC,eAAe,CAyBvD,CAAC;AASF,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;IAYtC,MAAM,CAAC,GAAG,EAAE,eAAe,GAAG,aAAa,CAAC,aAAa,CAAC;IAoM3D,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;CAQ1G"}
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
- parsed = { _rawPartial: entry.argsBuffer };
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
- if (entry.emittedStart) {
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
- const blob = (req.system ?? '') +
245
- req.messages.map((m) => m.content.map((c) => ('text' in c ? c.text : JSON.stringify(c))).join('')).join('') +
246
- (req.tools ?? []).map((t) => t.name + t.description).join('');
247
- return estimateTextTokens(blob);
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')
@@ -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":"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"}
@@ -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;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,aAAa,CAAC,eAAe,CAAC,GAAG,iBAAiB,EAAE,CA8E9F;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,aAAa,EAAE,CAS5E"}
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 message = { role: 'assistant', content: text || null };
58
- if (toolUses.length > 0) {
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: JSON.stringify(u.input ?? {}) },
87
+ function: { name: u.name, arguments: safeStringifyArgs(u.input) },
63
88
  }));
64
89
  }
65
90
  out.push(message);
@@ -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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"}
@@ -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,CAYpG"}
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
- return { ok: false, message: err instanceof Error ? err.message : String(err) };
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
  }
@@ -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;IAClF,CAAC;AACH,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"}
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.0",
3
+ "version": "0.1.1",
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,15 +45,15 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "openai": "^4.80.0",
48
- "@moxxy/sdk": "0.15.0"
48
+ "@moxxy/sdk": "0.15.1"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "^22.10.0",
52
52
  "typescript": "^5.7.3",
53
53
  "vitest": "^2.1.8",
54
54
  "zod": "^3.24.0",
55
- "@moxxy/tsconfig": "0.0.0",
56
- "@moxxy/vitest-preset": "0.0.0"
55
+ "@moxxy/vitest-preset": "0.0.0",
56
+ "@moxxy/tsconfig": "0.0.0"
57
57
  },
58
58
  "scripts": {
59
59
  "build": "tsc -p tsconfig.json",
@@ -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: resolveApiKey(cfg),
111
+ apiKey,
94
112
  name: spec.name,
95
113
  baseURL: resolveBaseURL(cfg),
96
114
  defaultModel: cfg.defaultModel ?? spec.defaultModel,
@@ -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
- parsed = { _rawPartial: entry.argsBuffer };
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
- if (entry.emittedStart) {
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
- const blob =
300
- (req.system ?? '') +
301
- req.messages.map((m) => m.content.map((c) => ('text' in c ? c.text : JSON.stringify(c))).join('')).join('') +
302
- (req.tools ?? []).map((t) => t.name + t.description).join('');
303
- return estimateTextTokens(blob);
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';
@@ -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 message: OpenAIChatMessage = { role: 'assistant', content: text || null };
85
- if (toolUses.length > 0) {
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: JSON.stringify(u.input ?? {}) },
114
+ function: { name: u.name, arguments: safeStringifyArgs(u.input) },
90
115
  }));
91
116
  }
92
117
  out.push(message);
@@ -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
- return { ok: false, message: err instanceof Error ? err.message : String(err) };
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> };