@rcrsr/rill-ext-openai 0.18.4 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +295 -111
  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,13 +209,13 @@ 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 {};
@@ -153,7 +265,7 @@ function buildJsonSchemaFromStructuralType(type, params) {
153
265
  return buildDictSchema(type);
154
266
  }
155
267
  if (type.kind !== "closure") {
156
- throw new RuntimeError3("RILL-R004", `unsupported schema kind: ${type.kind} (expected dict or closure)`);
268
+ throw new RuntimeError2("RILL-R005", `unsupported schema kind: ${type.kind} (expected dict or closure)`);
157
269
  }
158
270
  const properties = {};
159
271
  const required = [];
@@ -181,30 +293,51 @@ function buildJsonSchemaFromStructuralType(type, params) {
181
293
  }
182
294
 
183
295
  // ../../shared/ext-llm/dist/tool-loop.js
296
+ function readHaltMessage(halt) {
297
+ const status = getStatus(halt.value);
298
+ if (typeof status.message === "string" && status.message.length > 0) {
299
+ return status.message;
300
+ }
301
+ return halt.message;
302
+ }
303
+ function throwToolLoopHalt(ctx, code, kind, message) {
304
+ if (ctx !== void 0) {
305
+ const hostCtx = ctx;
306
+ if (typeof hostCtx.invalidate === "function") {
307
+ const invalid = hostCtx.invalidate(new Error(message), {
308
+ code,
309
+ provider: "tool_loop",
310
+ raw: { kind, message }
311
+ });
312
+ throw new RuntimeHaltSignal2(invalid, true);
313
+ }
314
+ }
315
+ throw new RuntimeError3("RILL-R005", message);
316
+ }
184
317
  async function executeToolCall(toolName, toolInput, tools, context) {
185
318
  if (!isDict(tools)) {
186
- throw new RuntimeError4("RILL-R004", "tool_loop: tools must be a dict of name \u2192 callable");
319
+ throwToolLoopHalt(context, "INVALID_INPUT", "tools_not_dict", "tool_loop: tools must be a dict of name \u2192 callable");
187
320
  }
188
321
  const toolsDict = tools;
189
322
  const toolFn = toolsDict[toolName];
190
323
  if (toolFn === void 0 || toolFn === null) {
191
- throw new RuntimeError4("RILL-R004", `Unknown tool: ${toolName}`);
324
+ throwToolLoopHalt(context, "NOT_FOUND", "unknown_tool", `Unknown tool: ${toolName}`);
192
325
  }
193
326
  if (!isCallable(toolFn)) {
194
- throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: tool must be callable`);
327
+ throwToolLoopHalt(context, "INVALID_INPUT", "tool_not_callable", `Invalid tool input for ${toolName}: tool must be callable`);
195
328
  }
196
329
  if (typeof toolInput !== "object" || toolInput === null) {
197
- throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: input must be an object`);
330
+ throwToolLoopHalt(context, "INVALID_INPUT", "invalid_tool_input", `Invalid tool input for ${toolName}: input must be an object`);
198
331
  }
199
332
  const callable = toolFn;
200
333
  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`);
334
+ throwToolLoopHalt(context, "INVALID_INPUT", "tool_kind_unsupported", `Invalid tool input for ${toolName}: tool must be application, runtime, or script callable`);
202
335
  }
203
336
  try {
204
337
  const inputDict = toolInput;
205
338
  if (callable.kind === "script") {
206
339
  if (!context) {
207
- throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: script callable requires a runtime context`);
340
+ throw new RuntimeError3("RILL-R005", `Invalid tool input for ${toolName}: script callable requires a runtime context`);
208
341
  }
209
342
  let args;
210
343
  if (callable.params && callable.params.length > 0) {
@@ -220,16 +353,17 @@ async function executeToolCall(toolName, toolInput, tools, context) {
220
353
  const ctx = context ?? {
221
354
  parent: void 0,
222
355
  variables: /* @__PURE__ */ new Map(),
223
- pipeValue: null
356
+ pipeValue: null,
357
+ hostContext: {}
224
358
  };
225
359
  const result = callable.fn(inputDict, ctx);
226
360
  return result instanceof Promise ? await result : result;
227
361
  } catch (error) {
228
- if (error instanceof RuntimeError4) {
362
+ if (error instanceof RuntimeError3) {
229
363
  throw error;
230
364
  }
231
365
  const message = error instanceof Error ? error.message : "Unknown error";
232
- throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: ${message}`);
366
+ throw new RuntimeError3("RILL-R005", `Invalid tool input for ${toolName}: ${message}`);
233
367
  }
234
368
  }
235
369
  function sanitizeToolName(name) {
@@ -300,19 +434,19 @@ function patchResponseToolCallNames(response, nameMap) {
300
434
  }
301
435
  async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent, maxTurns = 10, context, yieldChunk, signal) {
302
436
  if (tools === void 0) {
303
- throw new RuntimeError4("RILL-R004", "tools parameter is required");
437
+ throwToolLoopHalt(context, "INVALID_INPUT", "tools_required", "tools parameter is required");
304
438
  }
305
439
  if (!isDict(tools)) {
306
- throw new RuntimeError4("RILL-R004", "tool_loop: tools must be a dict of name \u2192 callable");
440
+ throwToolLoopHalt(context, "INVALID_INPUT", "tools_not_dict", "tool_loop: tools must be a dict of name \u2192 callable");
307
441
  }
308
442
  const toolsDict = tools;
309
443
  const toolDescriptors = Object.entries(toolsDict).map(([name, fn]) => {
310
444
  const fnValue = fn;
311
445
  if (isRuntimeCallable(fnValue)) {
312
- throw new RuntimeError4("RILL-R004", `tool_loop: builtin "${name}" cannot be used as a tool \u2014 wrap in a closure`);
446
+ throwToolLoopHalt(context, "INVALID_INPUT", "builtin_tool_unsupported", `tool_loop: builtin "${name}" cannot be used as a tool \u2014 wrap in a closure`);
313
447
  }
314
448
  if (!isCallable(fnValue)) {
315
- throw new RuntimeError4("RILL-R004", `tool_loop: tool "${name}" is not a callable`);
449
+ throwToolLoopHalt(context, "INVALID_INPUT", "tool_not_callable", `tool_loop: tool "${name}" is not a callable`);
316
450
  }
317
451
  const callable = fnValue;
318
452
  const description = callable.annotations?.["description"] ?? "";
@@ -347,7 +481,7 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
347
481
  let turnCount = 0;
348
482
  while (turnCount < maxTurns) {
349
483
  if (signal?.aborted) {
350
- throw new RuntimeError4("RILL-R004", "tool_loop cancelled");
484
+ throwToolLoopHalt(context, "TIMEOUT", "tool_loop_cancelled", "tool_loop cancelled");
351
485
  }
352
486
  turnCount++;
353
487
  let response;
@@ -361,8 +495,18 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
361
495
  response = await callbacks.callAPI(currentMessages, providerTools, signal);
362
496
  }
363
497
  } catch (error) {
498
+ if (error instanceof RuntimeHaltSignal2) {
499
+ throw error;
500
+ }
501
+ if (context !== void 0 && callbacks.detectError !== void 0) {
502
+ const hostCtx = context;
503
+ if (typeof hostCtx.invalidate === "function") {
504
+ const invalid = mapProviderError(hostCtx, "tool_loop", error, callbacks.detectError);
505
+ throw new RuntimeHaltSignal2(invalid, true);
506
+ }
507
+ }
364
508
  const message = error instanceof Error ? error.message : "Unknown error";
365
- throw new RuntimeError4("RILL-R004", `Provider API error: ${message}`, void 0, { cause: error });
509
+ throw new RuntimeError3("RILL-R005", `Provider API error: ${message}`, void 0, { cause: error });
366
510
  }
367
511
  if (typeof response === "object" && response !== null && "usage" in response) {
368
512
  const usage = response["usage"];
@@ -419,7 +563,11 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
419
563
  const duration = Date.now() - toolStartTime;
420
564
  consecutiveErrors++;
421
565
  let originalError;
422
- if (error instanceof RuntimeError4) {
566
+ if (error instanceof RuntimeHaltSignal2) {
567
+ const haltMessage = readHaltMessage(error);
568
+ const prefix = `Invalid tool input for ${name}: `;
569
+ originalError = haltMessage.startsWith(prefix) ? haltMessage.slice(prefix.length) : haltMessage;
570
+ } else if (error instanceof RuntimeError3) {
423
571
  const prefix = `Invalid tool input for ${name}: `;
424
572
  if (error.message.startsWith(prefix)) {
425
573
  originalError = error.message.slice(prefix.length);
@@ -444,7 +592,7 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
444
592
  duration
445
593
  });
446
594
  if (consecutiveErrors >= maxErrors) {
447
- throw new RuntimeError4("RILL-R004", `Tool execution failed: ${maxErrors} consecutive errors (last: ${name}: ${originalError})`);
595
+ throwToolLoopHalt(context, "UNAVAILABLE", "consecutive_tool_errors", `Tool execution failed: ${maxErrors} consecutive errors (last: ${name}: ${originalError})`);
448
596
  }
449
597
  }
450
598
  }
@@ -471,13 +619,13 @@ function buildResponseMessages(inputMessages, assistantContent) {
471
619
  }
472
620
 
473
621
  // ../../shared/ext-param/dist/param.js
474
- import { RuntimeError as RuntimeError5 } from "@rcrsr/rill";
622
+ import { RuntimeError as RuntimeError4 } from "@rcrsr/rill";
475
623
  function validateParamName(name) {
476
624
  if (name === "") {
477
- throw new RuntimeError5("RILL-R001", "param name must not be empty");
625
+ throw new RuntimeError4("RILL-R001", "param name must not be empty");
478
626
  }
479
627
  if (/\s/.test(name)) {
480
- throw new RuntimeError5("RILL-R001", "param name must be a valid identifier");
628
+ throw new RuntimeError4("RILL-R001", "param name must be a valid identifier");
481
629
  }
482
630
  }
483
631
  function buildAnnotations(desc) {
@@ -603,6 +751,16 @@ var detectOpenAIError = (error) => {
603
751
  }
604
752
  return null;
605
753
  };
754
+ function haltInvalid(ctx, code, rawKind, message) {
755
+ return new RuntimeHaltSignal3(
756
+ ctx.invalidate(new Error(message), {
757
+ code,
758
+ provider: "openai",
759
+ raw: { kind: rawKind, message }
760
+ }),
761
+ true
762
+ );
763
+ }
606
764
  function createOpenAIExtension(config) {
607
765
  validateApiKey(config.api_key);
608
766
  validateModel(config.model);
@@ -650,7 +808,7 @@ function createOpenAIExtension(config) {
650
808
  const text = args["text"];
651
809
  const options = args["options"] ?? {};
652
810
  if (text.trim().length === 0) {
653
- throw new RuntimeError6("RILL-R004", "prompt text cannot be empty");
811
+ throw haltInvalid(ctx, "INVALID_INPUT", "empty_prompt", "prompt text cannot be empty");
654
812
  }
655
813
  const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
656
814
  const maxTokens = typeof options["max_tokens"] === "number" && options["max_tokens"] > 0 ? options["max_tokens"] : factoryMaxTokens;
@@ -681,7 +839,7 @@ function createOpenAIExtension(config) {
681
839
  }
682
840
  }
683
841
  } catch (error) {
684
- throw mapProviderError("OpenAI", error, detectOpenAIError);
842
+ throwProviderHalt(ctx, "OpenAI", error, detectOpenAIError);
685
843
  }
686
844
  }
687
845
  const resolve = async () => {
@@ -719,14 +877,23 @@ function createOpenAIExtension(config) {
719
877
  return result;
720
878
  } catch (error) {
721
879
  const duration = Date.now() - startTime;
722
- const rillError = mapProviderError("OpenAI", error, detectOpenAIError);
880
+ if (error instanceof RuntimeHaltSignal3) {
881
+ emitExtensionEvent(ctx, {
882
+ event: "openai:error",
883
+ subsystem: "extension:openai",
884
+ error: getStatus2(error.value).message,
885
+ duration
886
+ });
887
+ throw error;
888
+ }
889
+ const invalid = mapProviderError(ctx, "OpenAI", error, detectOpenAIError);
723
890
  emitExtensionEvent(ctx, {
724
891
  event: "openai:error",
725
892
  subsystem: "extension:openai",
726
- error: rillError.message,
893
+ error: getStatus2(invalid).message,
727
894
  duration
728
895
  });
729
- throw rillError;
896
+ throw new RuntimeHaltSignal3(invalid, true);
730
897
  }
731
898
  };
732
899
  const retType = {
@@ -780,10 +947,7 @@ function createOpenAIExtension(config) {
780
947
  const messages = args["messages"];
781
948
  const options = args["options"] ?? {};
782
949
  if (messages.length === 0) {
783
- throw new RuntimeError6(
784
- "RILL-R004",
785
- "messages list cannot be empty"
786
- );
950
+ throw haltInvalid(ctx, "INVALID_INPUT", "empty_messages", "messages list cannot be empty");
787
951
  }
788
952
  const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
789
953
  const maxTokens = typeof options["max_tokens"] === "number" && options["max_tokens"] > 0 ? options["max_tokens"] : factoryMaxTokens;
@@ -797,21 +961,15 @@ function createOpenAIExtension(config) {
797
961
  for (let i = 0; i < messages.length; i++) {
798
962
  const msg = messages[i];
799
963
  if (!msg || typeof msg !== "object" || !("role" in msg)) {
800
- throw new RuntimeError6(
801
- "RILL-R004",
802
- "message missing required 'role' field"
803
- );
964
+ throw haltInvalid(ctx, "INVALID_INPUT", "invalid_message_format", "message missing required 'role' field");
804
965
  }
805
966
  const role = msg["role"];
806
967
  if (role !== "user" && role !== "assistant" && role !== "tool") {
807
- throw new RuntimeError6("RILL-R004", `invalid role '${role}'`);
968
+ throw haltInvalid(ctx, "INVALID_INPUT", "invalid_role", `invalid role '${String(role)}'`);
808
969
  }
809
970
  if (role === "user" || role === "tool") {
810
971
  if (!("content" in msg) || typeof msg["content"] !== "string") {
811
- throw new RuntimeError6(
812
- "RILL-R004",
813
- `${role} message requires 'content'`
814
- );
972
+ throw haltInvalid(ctx, "INVALID_INPUT", "missing_message_content", `${role} message requires 'content'`);
815
973
  }
816
974
  apiMessages.push({
817
975
  role,
@@ -821,10 +979,7 @@ function createOpenAIExtension(config) {
821
979
  const hasContent = "content" in msg && msg["content"];
822
980
  const hasToolCalls = "tool_calls" in msg && msg["tool_calls"];
823
981
  if (!hasContent && !hasToolCalls) {
824
- throw new RuntimeError6(
825
- "RILL-R004",
826
- "assistant message requires 'content' or 'tool_calls'"
827
- );
982
+ throw haltInvalid(ctx, "INVALID_INPUT", "invalid_assistant_message", "assistant message requires 'content' or 'tool_calls'");
828
983
  }
829
984
  if (hasContent) {
830
985
  apiMessages.push({
@@ -850,7 +1005,7 @@ function createOpenAIExtension(config) {
850
1005
  }
851
1006
  }
852
1007
  } catch (error) {
853
- throw mapProviderError("OpenAI", error, detectOpenAIError);
1008
+ throwProviderHalt(ctx, "OpenAI", error, detectOpenAIError);
854
1009
  }
855
1010
  }
856
1011
  const resolve = async () => {
@@ -888,14 +1043,23 @@ function createOpenAIExtension(config) {
888
1043
  return result;
889
1044
  } catch (error) {
890
1045
  const duration = Date.now() - startTime;
891
- const rillError = mapProviderError("OpenAI", error, detectOpenAIError);
1046
+ if (error instanceof RuntimeHaltSignal3) {
1047
+ emitExtensionEvent(ctx, {
1048
+ event: "openai:error",
1049
+ subsystem: "extension:openai",
1050
+ error: getStatus2(error.value).message,
1051
+ duration
1052
+ });
1053
+ throw error;
1054
+ }
1055
+ const invalid = mapProviderError(ctx, "OpenAI", error, detectOpenAIError);
892
1056
  emitExtensionEvent(ctx, {
893
1057
  event: "openai:error",
894
1058
  subsystem: "extension:openai",
895
- error: rillError.message,
1059
+ error: getStatus2(invalid).message,
896
1060
  duration
897
1061
  });
898
- throw rillError;
1062
+ throw new RuntimeHaltSignal3(invalid, true);
899
1063
  }
900
1064
  };
901
1065
  const retType = {
@@ -952,10 +1116,7 @@ function createOpenAIExtension(config) {
952
1116
  });
953
1117
  const embeddingData = response.data[0]?.embedding;
954
1118
  if (!embeddingData || embeddingData.length === 0) {
955
- throw new RuntimeError6(
956
- "RILL-R004",
957
- "OpenAI: empty embedding returned"
958
- );
1119
+ throw haltInvalid(ctx, "PROTOCOL", "empty_embedding_response", "OpenAI: empty embedding returned");
959
1120
  }
960
1121
  const float32Data = new Float32Array(embeddingData);
961
1122
  const vector = createVector(float32Data, factoryEmbedModel);
@@ -970,7 +1131,17 @@ function createOpenAIExtension(config) {
970
1131
  return vector;
971
1132
  } catch (error) {
972
1133
  const duration = Date.now() - startTime;
973
- const rillError = mapProviderError(
1134
+ if (error instanceof RuntimeHaltSignal3) {
1135
+ emitExtensionEvent(ctx, {
1136
+ event: "openai:error",
1137
+ subsystem: "extension:openai",
1138
+ error: getStatus2(error.value).message,
1139
+ duration
1140
+ });
1141
+ throw error;
1142
+ }
1143
+ const invalid = mapProviderError(
1144
+ ctx,
974
1145
  "OpenAI",
975
1146
  error,
976
1147
  detectOpenAIError
@@ -978,10 +1149,10 @@ function createOpenAIExtension(config) {
978
1149
  emitExtensionEvent(ctx, {
979
1150
  event: "openai:error",
980
1151
  subsystem: "extension:openai",
981
- error: rillError.message,
1152
+ error: getStatus2(invalid).message,
982
1153
  duration
983
1154
  });
984
- throw rillError;
1155
+ throw new RuntimeHaltSignal3(invalid, true);
985
1156
  }
986
1157
  },
987
1158
  annotations: { description: "Generate embedding vector for text" },
@@ -1008,10 +1179,7 @@ function createOpenAIExtension(config) {
1008
1179
  for (const embeddingItem of response.data) {
1009
1180
  const embeddingData = embeddingItem.embedding;
1010
1181
  if (!embeddingData || embeddingData.length === 0) {
1011
- throw new RuntimeError6(
1012
- "RILL-R004",
1013
- "OpenAI: empty embedding returned"
1014
- );
1182
+ throw haltInvalid(ctx, "PROTOCOL", "empty_embedding_response", "OpenAI: empty embedding returned");
1015
1183
  }
1016
1184
  const float32Data = new Float32Array(embeddingData);
1017
1185
  const vector = createVector(float32Data, factoryEmbedModel);
@@ -1031,7 +1199,17 @@ function createOpenAIExtension(config) {
1031
1199
  return vectors;
1032
1200
  } catch (error) {
1033
1201
  const duration = Date.now() - startTime;
1034
- const rillError = mapProviderError(
1202
+ if (error instanceof RuntimeHaltSignal3) {
1203
+ emitExtensionEvent(ctx, {
1204
+ event: "openai:error",
1205
+ subsystem: "extension:openai",
1206
+ error: getStatus2(error.value).message,
1207
+ duration
1208
+ });
1209
+ throw error;
1210
+ }
1211
+ const invalid = mapProviderError(
1212
+ ctx,
1035
1213
  "OpenAI",
1036
1214
  error,
1037
1215
  detectOpenAIError
@@ -1039,10 +1217,10 @@ function createOpenAIExtension(config) {
1039
1217
  emitExtensionEvent(ctx, {
1040
1218
  event: "openai:error",
1041
1219
  subsystem: "extension:openai",
1042
- error: rillError.message,
1220
+ error: getStatus2(invalid).message,
1043
1221
  duration
1044
1222
  });
1045
- throw rillError;
1223
+ throw new RuntimeHaltSignal3(invalid, true);
1046
1224
  }
1047
1225
  },
1048
1226
  annotations: { description: "Generate embedding vectors for multiple texts" },
@@ -1071,7 +1249,7 @@ function createOpenAIExtension(config) {
1071
1249
  const toolsDict = args["tools"];
1072
1250
  const options = args["options"] ?? {};
1073
1251
  if (prompt.trim().length === 0) {
1074
- throw new RuntimeError6("RILL-R004", "prompt text cannot be empty");
1252
+ throw haltInvalid(ctx, "INVALID_INPUT", "empty_prompt", "prompt text cannot be empty");
1075
1253
  }
1076
1254
  const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
1077
1255
  const maxTokens = typeof options["max_tokens"] === "number" && options["max_tokens"] > 0 ? options["max_tokens"] : factoryMaxTokens;
@@ -1088,20 +1266,14 @@ function createOpenAIExtension(config) {
1088
1266
  const prependedMessages = options["messages"];
1089
1267
  for (const msg of prependedMessages) {
1090
1268
  if (!msg || typeof msg !== "object" || !("role" in msg)) {
1091
- throw new RuntimeError6(
1092
- "RILL-R004",
1093
- "message missing required 'role' field"
1094
- );
1269
+ throw haltInvalid(ctx, "INVALID_INPUT", "invalid_message_format", "message missing required 'role' field");
1095
1270
  }
1096
1271
  const role = msg["role"];
1097
1272
  if (role !== "user" && role !== "assistant") {
1098
- throw new RuntimeError6("RILL-R004", `invalid role '${role}'`);
1273
+ throw haltInvalid(ctx, "INVALID_INPUT", "invalid_role", `invalid role '${String(role)}'`);
1099
1274
  }
1100
1275
  if (!("content" in msg) || typeof msg["content"] !== "string") {
1101
- throw new RuntimeError6(
1102
- "RILL-R004",
1103
- `${role} message requires 'content'`
1104
- );
1276
+ throw haltInvalid(ctx, "INVALID_INPUT", "missing_message_content", `${role} message requires 'content'`);
1105
1277
  }
1106
1278
  messages.push({
1107
1279
  role,
@@ -1278,8 +1450,8 @@ function createOpenAIExtension(config) {
1278
1450
  yield chunk;
1279
1451
  }
1280
1452
  } catch (error) {
1281
- const rillError = mapProviderError("OpenAI", error, detectOpenAIError);
1282
- throw rillError;
1453
+ if (error instanceof RuntimeHaltSignal3) throw error;
1454
+ throwProviderHalt(ctx, "OpenAI", error, detectOpenAIError);
1283
1455
  }
1284
1456
  }
1285
1457
  const resolve = async () => {
@@ -1320,14 +1492,23 @@ function createOpenAIExtension(config) {
1320
1492
  return result;
1321
1493
  } catch (error) {
1322
1494
  const duration = Date.now() - startTime;
1323
- const rillError = mapProviderError("OpenAI", error, detectOpenAIError);
1495
+ if (error instanceof RuntimeHaltSignal3) {
1496
+ emitExtensionEvent(ctx, {
1497
+ event: "openai:error",
1498
+ subsystem: "extension:openai",
1499
+ error: getStatus2(error.value).message,
1500
+ duration
1501
+ });
1502
+ throw error;
1503
+ }
1504
+ const invalid = mapProviderError(ctx, "OpenAI", error, detectOpenAIError);
1324
1505
  emitExtensionEvent(ctx, {
1325
1506
  event: "openai:error",
1326
1507
  subsystem: "extension:openai",
1327
- error: rillError.message,
1508
+ error: getStatus2(invalid).message,
1328
1509
  duration
1329
1510
  });
1330
- throw rillError;
1511
+ throw new RuntimeHaltSignal3(invalid, true);
1331
1512
  }
1332
1513
  };
1333
1514
  const retType = {
@@ -1386,16 +1567,10 @@ function createOpenAIExtension(config) {
1386
1567
  const schemaArg = args["schema"];
1387
1568
  const options = args["options"] ?? {};
1388
1569
  if (!schemaArg || !schemaArg.__rill_type || !schemaArg.structure) {
1389
- throw new RuntimeError6(
1390
- "RILL-R004",
1391
- "generate requires a type expression as schema"
1392
- );
1570
+ throw haltInvalid(ctx, "INVALID_INPUT", "invalid_schema", "generate requires a type expression as schema");
1393
1571
  }
1394
1572
  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
- );
1573
+ throw haltInvalid(ctx, "INVALID_INPUT", "invalid_schema_type", `generate requires a dict type as schema, got ${schemaArg.structure.kind}`);
1399
1574
  }
1400
1575
  const jsonSchema = buildJsonSchemaFromStructuralType(schemaArg.structure);
1401
1576
  const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
@@ -1411,20 +1586,14 @@ function createOpenAIExtension(config) {
1411
1586
  const prependedMessages = options["messages"];
1412
1587
  for (const msg of prependedMessages) {
1413
1588
  if (!msg || typeof msg !== "object" || !("role" in msg)) {
1414
- throw new RuntimeError6(
1415
- "RILL-R004",
1416
- "message missing required 'role' field"
1417
- );
1589
+ throw haltInvalid(ctx, "INVALID_INPUT", "invalid_message_format", "message missing required 'role' field");
1418
1590
  }
1419
1591
  const role = msg["role"];
1420
1592
  if (role !== "user" && role !== "assistant") {
1421
- throw new RuntimeError6("RILL-R004", `invalid role '${role}'`);
1593
+ throw haltInvalid(ctx, "INVALID_INPUT", "invalid_role", `invalid role '${String(role)}'`);
1422
1594
  }
1423
1595
  if (!("content" in msg) || typeof msg["content"] !== "string") {
1424
- throw new RuntimeError6(
1425
- "RILL-R004",
1426
- `${role} message requires 'content'`
1427
- );
1596
+ throw haltInvalid(ctx, "INVALID_INPUT", "missing_message_content", `${role} message requires 'content'`);
1428
1597
  }
1429
1598
  apiMessages.push({
1430
1599
  role,
@@ -1456,10 +1625,7 @@ function createOpenAIExtension(config) {
1456
1625
  data = JSON.parse(raw);
1457
1626
  } catch (parseError) {
1458
1627
  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
- );
1628
+ throw haltInvalid(ctx, "PROTOCOL", "json_parse_failed", `generate: failed to parse response JSON: ${detail}`);
1463
1629
  }
1464
1630
  const result = {
1465
1631
  data,
@@ -1485,14 +1651,32 @@ function createOpenAIExtension(config) {
1485
1651
  return result;
1486
1652
  } catch (error) {
1487
1653
  const duration = Date.now() - startTime;
1488
- const rillError = error instanceof RuntimeError6 ? error : mapProviderError("OpenAI", error, detectOpenAIError);
1654
+ if (error instanceof RuntimeError5) {
1655
+ emitExtensionEvent(ctx, {
1656
+ event: "openai:error",
1657
+ subsystem: "extension:openai",
1658
+ error: error.message,
1659
+ duration
1660
+ });
1661
+ throw error;
1662
+ }
1663
+ if (error instanceof RuntimeHaltSignal3) {
1664
+ emitExtensionEvent(ctx, {
1665
+ event: "openai:error",
1666
+ subsystem: "extension:openai",
1667
+ error: getStatus2(error.value).message,
1668
+ duration
1669
+ });
1670
+ throw error;
1671
+ }
1672
+ const invalid = mapProviderError(ctx, "OpenAI", error, detectOpenAIError);
1489
1673
  emitExtensionEvent(ctx, {
1490
1674
  event: "openai:error",
1491
1675
  subsystem: "extension:openai",
1492
- error: rillError.message,
1676
+ error: getStatus2(invalid).message,
1493
1677
  duration
1494
1678
  });
1495
- throw rillError;
1679
+ throw new RuntimeHaltSignal3(invalid, true);
1496
1680
  }
1497
1681
  },
1498
1682
  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.0",
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"