@rcrsr/rill-ext-openai 0.18.4 → 0.19.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.
Files changed (2) hide show
  1. package/dist/index.js +305 -116
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -4,11 +4,13 @@ import { createRequire } from "module";
4
4
  // src/factory.ts
5
5
  import OpenAI from "openai";
6
6
  import {
7
- RuntimeError as RuntimeError6,
7
+ RuntimeError as RuntimeError5,
8
+ RuntimeHaltSignal as RuntimeHaltSignal3,
8
9
  createRillStream,
9
10
  emitExtensionEvent,
10
11
  createVector,
11
12
  isVector,
13
+ getStatus as getStatus2,
12
14
  structureToTypeValue,
13
15
  toCallable
14
16
  } from "@rcrsr/rill";
@@ -64,27 +66,137 @@ function validateEmbedModel(model) {
64
66
  }
65
67
 
66
68
  // ../../shared/ext-llm/dist/errors.js
67
- import { RuntimeError as RuntimeError2 } from "@rcrsr/rill";
68
- function mapProviderError(providerName, error, detect) {
69
+ import { RuntimeHaltSignal } from "@rcrsr/rill";
70
+ function atomForStatus(status) {
71
+ if (status === 401)
72
+ return "AUTH";
73
+ if (status === 403)
74
+ return "FORBIDDEN";
75
+ if (status === 404)
76
+ return "NOT_FOUND";
77
+ if (status === 408)
78
+ return "TIMEOUT";
79
+ if (status === 409 || status === 412)
80
+ return "CONFLICT";
81
+ if (status === 429)
82
+ return "RATE_LIMIT";
83
+ if (status === 402)
84
+ return "QUOTA_EXCEEDED";
85
+ if (status >= 500 && status <= 599)
86
+ return "UNAVAILABLE";
87
+ if (status >= 400 && status <= 499)
88
+ return "INVALID_INPUT";
89
+ return "UNAVAILABLE";
90
+ }
91
+ function kindForStatus(status) {
92
+ if (status === 401)
93
+ return "authentication_failed";
94
+ if (status === 403)
95
+ return "forbidden";
96
+ if (status === 404)
97
+ return "not_found";
98
+ if (status === 408)
99
+ return "request_timeout";
100
+ if (status === 409 || status === 412)
101
+ return "conflict";
102
+ if (status === 429)
103
+ return "rate_limit_exceeded";
104
+ if (status === 402)
105
+ return "quota_exceeded";
106
+ if (status >= 500 && status <= 599)
107
+ return "server_error";
108
+ return "http_error";
109
+ }
110
+ function mapProviderError(ctx, provider, error, detect) {
111
+ if (error instanceof RuntimeHaltSignal) {
112
+ return ctx.invalidate(error, {
113
+ code: "TIMEOUT",
114
+ provider,
115
+ raw: { kind: "request_cancelled", message: `${provider}: request cancelled` }
116
+ });
117
+ }
118
+ if (error instanceof Error && error.name === "AbortError") {
119
+ return ctx.invalidate(error, {
120
+ code: "TIMEOUT",
121
+ provider,
122
+ raw: {
123
+ kind: "request_timeout",
124
+ message: `${provider} error: ${error.message}`
125
+ }
126
+ });
127
+ }
69
128
  const detected = detect(error);
70
129
  if (detected !== null) {
71
130
  const { status, message } = detected;
72
131
  if (status !== void 0) {
73
- return new RuntimeError2("RILL-R004", `${providerName} API error (HTTP ${status}): ${message}`, void 0, { cause: error });
132
+ return ctx.invalidate(error, {
133
+ code: atomForStatus(status),
134
+ provider,
135
+ raw: {
136
+ kind: kindForStatus(status),
137
+ status,
138
+ message: `${provider} API error (HTTP ${status}): ${message}`
139
+ }
140
+ });
74
141
  }
75
- return new RuntimeError2("RILL-R004", `${providerName} API error: ${message}`, void 0, { cause: error });
142
+ return ctx.invalidate(error, {
143
+ code: "UNAVAILABLE",
144
+ provider,
145
+ raw: {
146
+ kind: "provider_error",
147
+ message: `${provider} API error: ${message}`
148
+ }
149
+ });
150
+ }
151
+ if (error instanceof TypeError) {
152
+ return ctx.invalidate(error, {
153
+ code: "UNAVAILABLE",
154
+ provider,
155
+ raw: {
156
+ kind: "connection_failed",
157
+ message: `${provider} error: ${error.message}`
158
+ }
159
+ });
160
+ }
161
+ if (error instanceof SyntaxError) {
162
+ return ctx.invalidate(error, {
163
+ code: "PROTOCOL",
164
+ provider,
165
+ raw: {
166
+ kind: "unexpected_response_format",
167
+ message: `${provider} error: ${error.message}`
168
+ }
169
+ });
76
170
  }
77
171
  if (error instanceof Error) {
78
- return new RuntimeError2("RILL-R004", `${providerName} error: ${error.message}`, void 0, { cause: error });
172
+ return ctx.invalidate(error, {
173
+ code: "UNAVAILABLE",
174
+ provider,
175
+ raw: {
176
+ kind: "unknown_error",
177
+ message: `${provider} error: ${error.message}`
178
+ }
179
+ });
79
180
  }
80
- return new RuntimeError2("RILL-R004", `${providerName} error: Unknown error`, void 0, { cause: error });
181
+ return ctx.invalidate(error, {
182
+ code: "UNAVAILABLE",
183
+ provider,
184
+ raw: {
185
+ kind: "unknown_error",
186
+ message: `${provider} error: Unknown error`
187
+ }
188
+ });
189
+ }
190
+ function throwProviderHalt(ctx, provider, error, detect) {
191
+ const invalid = mapProviderError(ctx, provider, error, detect);
192
+ throw new RuntimeHaltSignal(invalid, true);
81
193
  }
82
194
 
83
195
  // ../../shared/ext-llm/dist/tool-loop.js
84
- import { invokeCallable, isCallable, isDict, isRuntimeCallable, RuntimeError as RuntimeError4 } from "@rcrsr/rill";
196
+ import { getStatus, invokeCallable, isCallable, isDict, isRuntimeCallable, RuntimeError as RuntimeError3, RuntimeHaltSignal as RuntimeHaltSignal2 } from "@rcrsr/rill";
85
197
 
86
198
  // ../../shared/ext-llm/dist/schema.js
87
- import { RuntimeError as RuntimeError3 } from "@rcrsr/rill";
199
+ import { RuntimeError as RuntimeError2 } from "@rcrsr/rill";
88
200
  var RILL_TYPE_MAP = {
89
201
  string: "string",
90
202
  number: "number",
@@ -97,24 +209,24 @@ var RILL_TYPE_MAP = {
97
209
  function mapRillType(rillType) {
98
210
  const jsonType = RILL_TYPE_MAP[rillType];
99
211
  if (jsonType === void 0) {
100
- throw new RuntimeError3("RILL-R004", `unsupported type: ${rillType}`);
212
+ throw new RuntimeError2("RILL-R005", `unsupported type: ${rillType}`);
101
213
  }
102
214
  return jsonType;
103
215
  }
104
216
  function buildPropertyFromStructuralType(rillType) {
105
217
  if (rillType.kind === "closure" || rillType.kind === "tuple") {
106
- throw new RuntimeError3("RILL-R004", `unsupported type for JSON Schema: ${rillType.kind}`);
218
+ throw new RuntimeError2("RILL-R005", `unsupported type for JSON Schema: ${rillType.kind}`);
107
219
  }
108
220
  if (rillType.kind === "any") {
109
221
  return {};
110
222
  }
111
223
  if (rillType.kind === "list") {
112
224
  const listType = rillType;
113
- const property = { type: "array" };
225
+ const property2 = { type: "array" };
114
226
  if (listType.element !== void 0) {
115
- property.items = buildPropertyFromStructuralType(listType.element);
227
+ property2.items = buildPropertyFromStructuralType(listType.element);
116
228
  }
117
- return property;
229
+ return property2;
118
230
  }
119
231
  if (rillType.kind === "dict") {
120
232
  const dictType = rillType;
@@ -127,9 +239,14 @@ function buildPropertyFromStructuralType(rillType) {
127
239
  additionalProperties: false
128
240
  };
129
241
  }
130
- return { type: "object" };
242
+ return { type: "object", additionalProperties: false };
243
+ }
244
+ const jsonType = mapRillType(rillType.kind);
245
+ const property = { type: jsonType };
246
+ if (jsonType === "object") {
247
+ property.additionalProperties = false;
131
248
  }
132
- return { type: mapRillType(rillType.kind) };
249
+ return property;
133
250
  }
134
251
  function buildDictSchema(dictType) {
135
252
  const properties = {};
@@ -153,7 +270,7 @@ function buildJsonSchemaFromStructuralType(type, params) {
153
270
  return buildDictSchema(type);
154
271
  }
155
272
  if (type.kind !== "closure") {
156
- throw new RuntimeError3("RILL-R004", `unsupported schema kind: ${type.kind} (expected dict or closure)`);
273
+ throw new RuntimeError2("RILL-R005", `unsupported schema kind: ${type.kind} (expected dict or closure)`);
157
274
  }
158
275
  const properties = {};
159
276
  const required = [];
@@ -181,30 +298,51 @@ function buildJsonSchemaFromStructuralType(type, params) {
181
298
  }
182
299
 
183
300
  // ../../shared/ext-llm/dist/tool-loop.js
301
+ function readHaltMessage(halt) {
302
+ const status = getStatus(halt.value);
303
+ if (typeof status.message === "string" && status.message.length > 0) {
304
+ return status.message;
305
+ }
306
+ return halt.message;
307
+ }
308
+ function throwToolLoopHalt(ctx, code, kind, message) {
309
+ if (ctx !== void 0) {
310
+ const hostCtx = ctx;
311
+ if (typeof hostCtx.invalidate === "function") {
312
+ const invalid = hostCtx.invalidate(new Error(message), {
313
+ code,
314
+ provider: "tool_loop",
315
+ raw: { kind, message }
316
+ });
317
+ throw new RuntimeHaltSignal2(invalid, true);
318
+ }
319
+ }
320
+ throw new RuntimeError3("RILL-R005", message);
321
+ }
184
322
  async function executeToolCall(toolName, toolInput, tools, context) {
185
323
  if (!isDict(tools)) {
186
- throw new RuntimeError4("RILL-R004", "tool_loop: tools must be a dict of name \u2192 callable");
324
+ throwToolLoopHalt(context, "INVALID_INPUT", "tools_not_dict", "tool_loop: tools must be a dict of name \u2192 callable");
187
325
  }
188
326
  const toolsDict = tools;
189
327
  const toolFn = toolsDict[toolName];
190
328
  if (toolFn === void 0 || toolFn === null) {
191
- throw new RuntimeError4("RILL-R004", `Unknown tool: ${toolName}`);
329
+ throwToolLoopHalt(context, "NOT_FOUND", "unknown_tool", `Unknown tool: ${toolName}`);
192
330
  }
193
331
  if (!isCallable(toolFn)) {
194
- throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: tool must be callable`);
332
+ throwToolLoopHalt(context, "INVALID_INPUT", "tool_not_callable", `Invalid tool input for ${toolName}: tool must be callable`);
195
333
  }
196
334
  if (typeof toolInput !== "object" || toolInput === null) {
197
- throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: input must be an object`);
335
+ throwToolLoopHalt(context, "INVALID_INPUT", "invalid_tool_input", `Invalid tool input for ${toolName}: input must be an object`);
198
336
  }
199
337
  const callable = toolFn;
200
338
  if (callable.kind !== "runtime" && callable.kind !== "application" && callable.kind !== "script") {
201
- throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: tool must be application, runtime, or script callable`);
339
+ throwToolLoopHalt(context, "INVALID_INPUT", "tool_kind_unsupported", `Invalid tool input for ${toolName}: tool must be application, runtime, or script callable`);
202
340
  }
203
341
  try {
204
342
  const inputDict = toolInput;
205
343
  if (callable.kind === "script") {
206
344
  if (!context) {
207
- throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: script callable requires a runtime context`);
345
+ throw new RuntimeError3("RILL-R005", `Invalid tool input for ${toolName}: script callable requires a runtime context`);
208
346
  }
209
347
  let args;
210
348
  if (callable.params && callable.params.length > 0) {
@@ -220,16 +358,17 @@ async function executeToolCall(toolName, toolInput, tools, context) {
220
358
  const ctx = context ?? {
221
359
  parent: void 0,
222
360
  variables: /* @__PURE__ */ new Map(),
223
- pipeValue: null
361
+ pipeValue: null,
362
+ hostContext: {}
224
363
  };
225
364
  const result = callable.fn(inputDict, ctx);
226
365
  return result instanceof Promise ? await result : result;
227
366
  } catch (error) {
228
- if (error instanceof RuntimeError4) {
367
+ if (error instanceof RuntimeError3) {
229
368
  throw error;
230
369
  }
231
370
  const message = error instanceof Error ? error.message : "Unknown error";
232
- throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: ${message}`);
371
+ throw new RuntimeError3("RILL-R005", `Invalid tool input for ${toolName}: ${message}`);
233
372
  }
234
373
  }
235
374
  function sanitizeToolName(name) {
@@ -300,19 +439,19 @@ function patchResponseToolCallNames(response, nameMap) {
300
439
  }
301
440
  async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent, maxTurns = 10, context, yieldChunk, signal) {
302
441
  if (tools === void 0) {
303
- throw new RuntimeError4("RILL-R004", "tools parameter is required");
442
+ throwToolLoopHalt(context, "INVALID_INPUT", "tools_required", "tools parameter is required");
304
443
  }
305
444
  if (!isDict(tools)) {
306
- throw new RuntimeError4("RILL-R004", "tool_loop: tools must be a dict of name \u2192 callable");
445
+ throwToolLoopHalt(context, "INVALID_INPUT", "tools_not_dict", "tool_loop: tools must be a dict of name \u2192 callable");
307
446
  }
308
447
  const toolsDict = tools;
309
448
  const toolDescriptors = Object.entries(toolsDict).map(([name, fn]) => {
310
449
  const fnValue = fn;
311
450
  if (isRuntimeCallable(fnValue)) {
312
- throw new RuntimeError4("RILL-R004", `tool_loop: builtin "${name}" cannot be used as a tool \u2014 wrap in a closure`);
451
+ throwToolLoopHalt(context, "INVALID_INPUT", "builtin_tool_unsupported", `tool_loop: builtin "${name}" cannot be used as a tool \u2014 wrap in a closure`);
313
452
  }
314
453
  if (!isCallable(fnValue)) {
315
- throw new RuntimeError4("RILL-R004", `tool_loop: tool "${name}" is not a callable`);
454
+ throwToolLoopHalt(context, "INVALID_INPUT", "tool_not_callable", `tool_loop: tool "${name}" is not a callable`);
316
455
  }
317
456
  const callable = fnValue;
318
457
  const description = callable.annotations?.["description"] ?? "";
@@ -347,7 +486,7 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
347
486
  let turnCount = 0;
348
487
  while (turnCount < maxTurns) {
349
488
  if (signal?.aborted) {
350
- throw new RuntimeError4("RILL-R004", "tool_loop cancelled");
489
+ throwToolLoopHalt(context, "TIMEOUT", "tool_loop_cancelled", "tool_loop cancelled");
351
490
  }
352
491
  turnCount++;
353
492
  let response;
@@ -361,8 +500,18 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
361
500
  response = await callbacks.callAPI(currentMessages, providerTools, signal);
362
501
  }
363
502
  } catch (error) {
503
+ if (error instanceof RuntimeHaltSignal2) {
504
+ throw error;
505
+ }
506
+ if (context !== void 0 && callbacks.detectError !== void 0) {
507
+ const hostCtx = context;
508
+ if (typeof hostCtx.invalidate === "function") {
509
+ const invalid = mapProviderError(hostCtx, "tool_loop", error, callbacks.detectError);
510
+ throw new RuntimeHaltSignal2(invalid, true);
511
+ }
512
+ }
364
513
  const message = error instanceof Error ? error.message : "Unknown error";
365
- throw new RuntimeError4("RILL-R004", `Provider API error: ${message}`, void 0, { cause: error });
514
+ throw new RuntimeError3("RILL-R005", `Provider API error: ${message}`, void 0, { cause: error });
366
515
  }
367
516
  if (typeof response === "object" && response !== null && "usage" in response) {
368
517
  const usage = response["usage"];
@@ -419,7 +568,11 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
419
568
  const duration = Date.now() - toolStartTime;
420
569
  consecutiveErrors++;
421
570
  let originalError;
422
- if (error instanceof RuntimeError4) {
571
+ if (error instanceof RuntimeHaltSignal2) {
572
+ const haltMessage = readHaltMessage(error);
573
+ const prefix = `Invalid tool input for ${name}: `;
574
+ originalError = haltMessage.startsWith(prefix) ? haltMessage.slice(prefix.length) : haltMessage;
575
+ } else if (error instanceof RuntimeError3) {
423
576
  const prefix = `Invalid tool input for ${name}: `;
424
577
  if (error.message.startsWith(prefix)) {
425
578
  originalError = error.message.slice(prefix.length);
@@ -444,7 +597,7 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
444
597
  duration
445
598
  });
446
599
  if (consecutiveErrors >= maxErrors) {
447
- throw new RuntimeError4("RILL-R004", `Tool execution failed: ${maxErrors} consecutive errors (last: ${name}: ${originalError})`);
600
+ throwToolLoopHalt(context, "UNAVAILABLE", "consecutive_tool_errors", `Tool execution failed: ${maxErrors} consecutive errors (last: ${name}: ${originalError})`);
448
601
  }
449
602
  }
450
603
  }
@@ -471,13 +624,13 @@ function buildResponseMessages(inputMessages, assistantContent) {
471
624
  }
472
625
 
473
626
  // ../../shared/ext-param/dist/param.js
474
- import { RuntimeError as RuntimeError5 } from "@rcrsr/rill";
627
+ import { RuntimeError as RuntimeError4 } from "@rcrsr/rill";
475
628
  function validateParamName(name) {
476
629
  if (name === "") {
477
- throw new RuntimeError5("RILL-R001", "param name must not be empty");
630
+ throw new RuntimeError4("RILL-R001", "param name must not be empty");
478
631
  }
479
632
  if (/\s/.test(name)) {
480
- throw new RuntimeError5("RILL-R001", "param name must be a valid identifier");
633
+ throw new RuntimeError4("RILL-R001", "param name must be a valid identifier");
481
634
  }
482
635
  }
483
636
  function buildAnnotations(desc) {
@@ -603,6 +756,16 @@ var detectOpenAIError = (error) => {
603
756
  }
604
757
  return null;
605
758
  };
759
+ function haltInvalid(ctx, code, rawKind, message) {
760
+ return new RuntimeHaltSignal3(
761
+ ctx.invalidate(new Error(message), {
762
+ code,
763
+ provider: "openai",
764
+ raw: { kind: rawKind, message }
765
+ }),
766
+ true
767
+ );
768
+ }
606
769
  function createOpenAIExtension(config) {
607
770
  validateApiKey(config.api_key);
608
771
  validateModel(config.model);
@@ -650,7 +813,7 @@ function createOpenAIExtension(config) {
650
813
  const text = args["text"];
651
814
  const options = args["options"] ?? {};
652
815
  if (text.trim().length === 0) {
653
- throw new RuntimeError6("RILL-R004", "prompt text cannot be empty");
816
+ throw haltInvalid(ctx, "INVALID_INPUT", "empty_prompt", "prompt text cannot be empty");
654
817
  }
655
818
  const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
656
819
  const maxTokens = typeof options["max_tokens"] === "number" && options["max_tokens"] > 0 ? options["max_tokens"] : factoryMaxTokens;
@@ -681,7 +844,7 @@ function createOpenAIExtension(config) {
681
844
  }
682
845
  }
683
846
  } catch (error) {
684
- throw mapProviderError("OpenAI", error, detectOpenAIError);
847
+ throwProviderHalt(ctx, "OpenAI", error, detectOpenAIError);
685
848
  }
686
849
  }
687
850
  const resolve = async () => {
@@ -719,14 +882,23 @@ function createOpenAIExtension(config) {
719
882
  return result;
720
883
  } catch (error) {
721
884
  const duration = Date.now() - startTime;
722
- const rillError = mapProviderError("OpenAI", error, detectOpenAIError);
885
+ if (error instanceof RuntimeHaltSignal3) {
886
+ emitExtensionEvent(ctx, {
887
+ event: "openai:error",
888
+ subsystem: "extension:openai",
889
+ error: getStatus2(error.value).message,
890
+ duration
891
+ });
892
+ throw error;
893
+ }
894
+ const invalid = mapProviderError(ctx, "OpenAI", error, detectOpenAIError);
723
895
  emitExtensionEvent(ctx, {
724
896
  event: "openai:error",
725
897
  subsystem: "extension:openai",
726
- error: rillError.message,
898
+ error: getStatus2(invalid).message,
727
899
  duration
728
900
  });
729
- throw rillError;
901
+ throw new RuntimeHaltSignal3(invalid, true);
730
902
  }
731
903
  };
732
904
  const retType = {
@@ -780,10 +952,7 @@ function createOpenAIExtension(config) {
780
952
  const messages = args["messages"];
781
953
  const options = args["options"] ?? {};
782
954
  if (messages.length === 0) {
783
- throw new RuntimeError6(
784
- "RILL-R004",
785
- "messages list cannot be empty"
786
- );
955
+ throw haltInvalid(ctx, "INVALID_INPUT", "empty_messages", "messages list cannot be empty");
787
956
  }
788
957
  const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
789
958
  const maxTokens = typeof options["max_tokens"] === "number" && options["max_tokens"] > 0 ? options["max_tokens"] : factoryMaxTokens;
@@ -797,21 +966,15 @@ function createOpenAIExtension(config) {
797
966
  for (let i = 0; i < messages.length; i++) {
798
967
  const msg = messages[i];
799
968
  if (!msg || typeof msg !== "object" || !("role" in msg)) {
800
- throw new RuntimeError6(
801
- "RILL-R004",
802
- "message missing required 'role' field"
803
- );
969
+ throw haltInvalid(ctx, "INVALID_INPUT", "invalid_message_format", "message missing required 'role' field");
804
970
  }
805
971
  const role = msg["role"];
806
972
  if (role !== "user" && role !== "assistant" && role !== "tool") {
807
- throw new RuntimeError6("RILL-R004", `invalid role '${role}'`);
973
+ throw haltInvalid(ctx, "INVALID_INPUT", "invalid_role", `invalid role '${String(role)}'`);
808
974
  }
809
975
  if (role === "user" || role === "tool") {
810
976
  if (!("content" in msg) || typeof msg["content"] !== "string") {
811
- throw new RuntimeError6(
812
- "RILL-R004",
813
- `${role} message requires 'content'`
814
- );
977
+ throw haltInvalid(ctx, "INVALID_INPUT", "missing_message_content", `${role} message requires 'content'`);
815
978
  }
816
979
  apiMessages.push({
817
980
  role,
@@ -821,10 +984,7 @@ function createOpenAIExtension(config) {
821
984
  const hasContent = "content" in msg && msg["content"];
822
985
  const hasToolCalls = "tool_calls" in msg && msg["tool_calls"];
823
986
  if (!hasContent && !hasToolCalls) {
824
- throw new RuntimeError6(
825
- "RILL-R004",
826
- "assistant message requires 'content' or 'tool_calls'"
827
- );
987
+ throw haltInvalid(ctx, "INVALID_INPUT", "invalid_assistant_message", "assistant message requires 'content' or 'tool_calls'");
828
988
  }
829
989
  if (hasContent) {
830
990
  apiMessages.push({
@@ -850,7 +1010,7 @@ function createOpenAIExtension(config) {
850
1010
  }
851
1011
  }
852
1012
  } catch (error) {
853
- throw mapProviderError("OpenAI", error, detectOpenAIError);
1013
+ throwProviderHalt(ctx, "OpenAI", error, detectOpenAIError);
854
1014
  }
855
1015
  }
856
1016
  const resolve = async () => {
@@ -888,14 +1048,23 @@ function createOpenAIExtension(config) {
888
1048
  return result;
889
1049
  } catch (error) {
890
1050
  const duration = Date.now() - startTime;
891
- const rillError = mapProviderError("OpenAI", error, detectOpenAIError);
1051
+ if (error instanceof RuntimeHaltSignal3) {
1052
+ emitExtensionEvent(ctx, {
1053
+ event: "openai:error",
1054
+ subsystem: "extension:openai",
1055
+ error: getStatus2(error.value).message,
1056
+ duration
1057
+ });
1058
+ throw error;
1059
+ }
1060
+ const invalid = mapProviderError(ctx, "OpenAI", error, detectOpenAIError);
892
1061
  emitExtensionEvent(ctx, {
893
1062
  event: "openai:error",
894
1063
  subsystem: "extension:openai",
895
- error: rillError.message,
1064
+ error: getStatus2(invalid).message,
896
1065
  duration
897
1066
  });
898
- throw rillError;
1067
+ throw new RuntimeHaltSignal3(invalid, true);
899
1068
  }
900
1069
  };
901
1070
  const retType = {
@@ -952,10 +1121,7 @@ function createOpenAIExtension(config) {
952
1121
  });
953
1122
  const embeddingData = response.data[0]?.embedding;
954
1123
  if (!embeddingData || embeddingData.length === 0) {
955
- throw new RuntimeError6(
956
- "RILL-R004",
957
- "OpenAI: empty embedding returned"
958
- );
1124
+ throw haltInvalid(ctx, "PROTOCOL", "empty_embedding_response", "OpenAI: empty embedding returned");
959
1125
  }
960
1126
  const float32Data = new Float32Array(embeddingData);
961
1127
  const vector = createVector(float32Data, factoryEmbedModel);
@@ -970,7 +1136,17 @@ function createOpenAIExtension(config) {
970
1136
  return vector;
971
1137
  } catch (error) {
972
1138
  const duration = Date.now() - startTime;
973
- const rillError = mapProviderError(
1139
+ if (error instanceof RuntimeHaltSignal3) {
1140
+ emitExtensionEvent(ctx, {
1141
+ event: "openai:error",
1142
+ subsystem: "extension:openai",
1143
+ error: getStatus2(error.value).message,
1144
+ duration
1145
+ });
1146
+ throw error;
1147
+ }
1148
+ const invalid = mapProviderError(
1149
+ ctx,
974
1150
  "OpenAI",
975
1151
  error,
976
1152
  detectOpenAIError
@@ -978,10 +1154,10 @@ function createOpenAIExtension(config) {
978
1154
  emitExtensionEvent(ctx, {
979
1155
  event: "openai:error",
980
1156
  subsystem: "extension:openai",
981
- error: rillError.message,
1157
+ error: getStatus2(invalid).message,
982
1158
  duration
983
1159
  });
984
- throw rillError;
1160
+ throw new RuntimeHaltSignal3(invalid, true);
985
1161
  }
986
1162
  },
987
1163
  annotations: { description: "Generate embedding vector for text" },
@@ -1008,10 +1184,7 @@ function createOpenAIExtension(config) {
1008
1184
  for (const embeddingItem of response.data) {
1009
1185
  const embeddingData = embeddingItem.embedding;
1010
1186
  if (!embeddingData || embeddingData.length === 0) {
1011
- throw new RuntimeError6(
1012
- "RILL-R004",
1013
- "OpenAI: empty embedding returned"
1014
- );
1187
+ throw haltInvalid(ctx, "PROTOCOL", "empty_embedding_response", "OpenAI: empty embedding returned");
1015
1188
  }
1016
1189
  const float32Data = new Float32Array(embeddingData);
1017
1190
  const vector = createVector(float32Data, factoryEmbedModel);
@@ -1031,7 +1204,17 @@ function createOpenAIExtension(config) {
1031
1204
  return vectors;
1032
1205
  } catch (error) {
1033
1206
  const duration = Date.now() - startTime;
1034
- const rillError = mapProviderError(
1207
+ if (error instanceof RuntimeHaltSignal3) {
1208
+ emitExtensionEvent(ctx, {
1209
+ event: "openai:error",
1210
+ subsystem: "extension:openai",
1211
+ error: getStatus2(error.value).message,
1212
+ duration
1213
+ });
1214
+ throw error;
1215
+ }
1216
+ const invalid = mapProviderError(
1217
+ ctx,
1035
1218
  "OpenAI",
1036
1219
  error,
1037
1220
  detectOpenAIError
@@ -1039,10 +1222,10 @@ function createOpenAIExtension(config) {
1039
1222
  emitExtensionEvent(ctx, {
1040
1223
  event: "openai:error",
1041
1224
  subsystem: "extension:openai",
1042
- error: rillError.message,
1225
+ error: getStatus2(invalid).message,
1043
1226
  duration
1044
1227
  });
1045
- throw rillError;
1228
+ throw new RuntimeHaltSignal3(invalid, true);
1046
1229
  }
1047
1230
  },
1048
1231
  annotations: { description: "Generate embedding vectors for multiple texts" },
@@ -1071,7 +1254,7 @@ function createOpenAIExtension(config) {
1071
1254
  const toolsDict = args["tools"];
1072
1255
  const options = args["options"] ?? {};
1073
1256
  if (prompt.trim().length === 0) {
1074
- throw new RuntimeError6("RILL-R004", "prompt text cannot be empty");
1257
+ throw haltInvalid(ctx, "INVALID_INPUT", "empty_prompt", "prompt text cannot be empty");
1075
1258
  }
1076
1259
  const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
1077
1260
  const maxTokens = typeof options["max_tokens"] === "number" && options["max_tokens"] > 0 ? options["max_tokens"] : factoryMaxTokens;
@@ -1088,20 +1271,14 @@ function createOpenAIExtension(config) {
1088
1271
  const prependedMessages = options["messages"];
1089
1272
  for (const msg of prependedMessages) {
1090
1273
  if (!msg || typeof msg !== "object" || !("role" in msg)) {
1091
- throw new RuntimeError6(
1092
- "RILL-R004",
1093
- "message missing required 'role' field"
1094
- );
1274
+ throw haltInvalid(ctx, "INVALID_INPUT", "invalid_message_format", "message missing required 'role' field");
1095
1275
  }
1096
1276
  const role = msg["role"];
1097
1277
  if (role !== "user" && role !== "assistant") {
1098
- throw new RuntimeError6("RILL-R004", `invalid role '${role}'`);
1278
+ throw haltInvalid(ctx, "INVALID_INPUT", "invalid_role", `invalid role '${String(role)}'`);
1099
1279
  }
1100
1280
  if (!("content" in msg) || typeof msg["content"] !== "string") {
1101
- throw new RuntimeError6(
1102
- "RILL-R004",
1103
- `${role} message requires 'content'`
1104
- );
1281
+ throw haltInvalid(ctx, "INVALID_INPUT", "missing_message_content", `${role} message requires 'content'`);
1105
1282
  }
1106
1283
  messages.push({
1107
1284
  role,
@@ -1278,8 +1455,8 @@ function createOpenAIExtension(config) {
1278
1455
  yield chunk;
1279
1456
  }
1280
1457
  } catch (error) {
1281
- const rillError = mapProviderError("OpenAI", error, detectOpenAIError);
1282
- throw rillError;
1458
+ if (error instanceof RuntimeHaltSignal3) throw error;
1459
+ throwProviderHalt(ctx, "OpenAI", error, detectOpenAIError);
1283
1460
  }
1284
1461
  }
1285
1462
  const resolve = async () => {
@@ -1320,14 +1497,23 @@ function createOpenAIExtension(config) {
1320
1497
  return result;
1321
1498
  } catch (error) {
1322
1499
  const duration = Date.now() - startTime;
1323
- const rillError = mapProviderError("OpenAI", error, detectOpenAIError);
1500
+ if (error instanceof RuntimeHaltSignal3) {
1501
+ emitExtensionEvent(ctx, {
1502
+ event: "openai:error",
1503
+ subsystem: "extension:openai",
1504
+ error: getStatus2(error.value).message,
1505
+ duration
1506
+ });
1507
+ throw error;
1508
+ }
1509
+ const invalid = mapProviderError(ctx, "OpenAI", error, detectOpenAIError);
1324
1510
  emitExtensionEvent(ctx, {
1325
1511
  event: "openai:error",
1326
1512
  subsystem: "extension:openai",
1327
- error: rillError.message,
1513
+ error: getStatus2(invalid).message,
1328
1514
  duration
1329
1515
  });
1330
- throw rillError;
1516
+ throw new RuntimeHaltSignal3(invalid, true);
1331
1517
  }
1332
1518
  };
1333
1519
  const retType = {
@@ -1386,16 +1572,10 @@ function createOpenAIExtension(config) {
1386
1572
  const schemaArg = args["schema"];
1387
1573
  const options = args["options"] ?? {};
1388
1574
  if (!schemaArg || !schemaArg.__rill_type || !schemaArg.structure) {
1389
- throw new RuntimeError6(
1390
- "RILL-R004",
1391
- "generate requires a type expression as schema"
1392
- );
1575
+ throw haltInvalid(ctx, "INVALID_INPUT", "invalid_schema", "generate requires a type expression as schema");
1393
1576
  }
1394
1577
  if (schemaArg.structure.kind !== "dict") {
1395
- throw new RuntimeError6(
1396
- "RILL-R004",
1397
- `generate requires a dict type as schema, got ${schemaArg.structure.kind}`
1398
- );
1578
+ throw haltInvalid(ctx, "INVALID_INPUT", "invalid_schema_type", `generate requires a dict type as schema, got ${schemaArg.structure.kind}`);
1399
1579
  }
1400
1580
  const jsonSchema = buildJsonSchemaFromStructuralType(schemaArg.structure);
1401
1581
  const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
@@ -1411,20 +1591,14 @@ function createOpenAIExtension(config) {
1411
1591
  const prependedMessages = options["messages"];
1412
1592
  for (const msg of prependedMessages) {
1413
1593
  if (!msg || typeof msg !== "object" || !("role" in msg)) {
1414
- throw new RuntimeError6(
1415
- "RILL-R004",
1416
- "message missing required 'role' field"
1417
- );
1594
+ throw haltInvalid(ctx, "INVALID_INPUT", "invalid_message_format", "message missing required 'role' field");
1418
1595
  }
1419
1596
  const role = msg["role"];
1420
1597
  if (role !== "user" && role !== "assistant") {
1421
- throw new RuntimeError6("RILL-R004", `invalid role '${role}'`);
1598
+ throw haltInvalid(ctx, "INVALID_INPUT", "invalid_role", `invalid role '${String(role)}'`);
1422
1599
  }
1423
1600
  if (!("content" in msg) || typeof msg["content"] !== "string") {
1424
- throw new RuntimeError6(
1425
- "RILL-R004",
1426
- `${role} message requires 'content'`
1427
- );
1601
+ throw haltInvalid(ctx, "INVALID_INPUT", "missing_message_content", `${role} message requires 'content'`);
1428
1602
  }
1429
1603
  apiMessages.push({
1430
1604
  role,
@@ -1456,10 +1630,7 @@ function createOpenAIExtension(config) {
1456
1630
  data = JSON.parse(raw);
1457
1631
  } catch (parseError) {
1458
1632
  const detail = parseError instanceof Error ? parseError.message : String(parseError);
1459
- throw new RuntimeError6(
1460
- "RILL-R004",
1461
- `generate: failed to parse response JSON: ${detail}`
1462
- );
1633
+ throw haltInvalid(ctx, "PROTOCOL", "json_parse_failed", `generate: failed to parse response JSON: ${detail}`);
1463
1634
  }
1464
1635
  const result = {
1465
1636
  data,
@@ -1485,14 +1656,32 @@ function createOpenAIExtension(config) {
1485
1656
  return result;
1486
1657
  } catch (error) {
1487
1658
  const duration = Date.now() - startTime;
1488
- const rillError = error instanceof RuntimeError6 ? error : mapProviderError("OpenAI", error, detectOpenAIError);
1659
+ if (error instanceof RuntimeError5) {
1660
+ emitExtensionEvent(ctx, {
1661
+ event: "openai:error",
1662
+ subsystem: "extension:openai",
1663
+ error: error.message,
1664
+ duration
1665
+ });
1666
+ throw error;
1667
+ }
1668
+ if (error instanceof RuntimeHaltSignal3) {
1669
+ emitExtensionEvent(ctx, {
1670
+ event: "openai:error",
1671
+ subsystem: "extension:openai",
1672
+ error: getStatus2(error.value).message,
1673
+ duration
1674
+ });
1675
+ throw error;
1676
+ }
1677
+ const invalid = mapProviderError(ctx, "OpenAI", error, detectOpenAIError);
1489
1678
  emitExtensionEvent(ctx, {
1490
1679
  event: "openai:error",
1491
1680
  subsystem: "extension:openai",
1492
- error: rillError.message,
1681
+ error: getStatus2(invalid).message,
1493
1682
  duration
1494
1683
  });
1495
- throw rillError;
1684
+ throw new RuntimeHaltSignal3(invalid, true);
1496
1685
  }
1497
1686
  },
1498
1687
  annotations: { description: "Generate structured output from OpenAI API" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rcrsr/rill-ext-openai",
3
- "version": "0.18.4",
3
+ "version": "0.19.1",
4
4
  "description": "rill extension for OpenAI API integration",
5
5
  "license": "MIT",
6
6
  "author": "Andre Bremer",
@@ -17,16 +17,16 @@
17
17
  "scripting"
18
18
  ],
19
19
  "peerDependencies": {
20
- "@rcrsr/rill": "~0.18.4"
20
+ "@rcrsr/rill": "~0.19.0"
21
21
  },
22
22
  "devDependencies": {
23
- "@rcrsr/rill": "~0.18.4",
23
+ "@rcrsr/rill": "~0.19.0",
24
24
  "@types/node": "^25.5.2",
25
25
  "dts-bundle-generator": "^9.5.1",
26
26
  "tsup": "^8.5.1",
27
27
  "undici-types": "^8.0.2",
28
- "@rcrsr/rill-ext-llm-shared": "^0.18.4",
29
- "@rcrsr/rill-ext-param-shared": "0.18.4"
28
+ "@rcrsr/rill-ext-llm-shared": "^0.19.0",
29
+ "@rcrsr/rill-ext-param-shared": "0.19.0"
30
30
  },
31
31
  "files": [
32
32
  "dist"