@columenlabs/lumen-ai 0.1.97 → 0.1.99

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.
@@ -133,6 +133,28 @@ function shownError(err) {
133
133
  }
134
134
  return { message: 'Something went wrong while answering.', code: 'internal' };
135
135
  }
136
+ /**
137
+ * A plain sentence for an error OpenAI sends inside the answer's stream (an `error` event, or a
138
+ * failed response): what happened and what to do, by its code. The code is shown when it is not
139
+ * one we know; OpenAI's own message is only logged.
140
+ */
141
+ function openaiStreamError(code, plan) {
142
+ if (/usage_limit|usage_unavailable|insufficient_quota|rate_limit/.test(code))
143
+ return plan
144
+ ? 'Your ChatGPT plan reached its usage limit for now: try again later, or pick another model.'
145
+ : 'OpenAI says the key is out of quota or rate limited. Try again shortly.';
146
+ if (/context_length|too_long|max_tokens/.test(code))
147
+ return 'This chat is too long for the model. Start a new chat, or type /compact.';
148
+ if (/model_not_found|model_not_supported|invalid_model|unsupported_model/.test(code))
149
+ return plan
150
+ ? 'This model is not on your ChatGPT plan. Pick another model for this tier in Settings.'
151
+ : 'OpenAI does not offer this model to the key. Pick another model for this tier in Settings.';
152
+ if (/server_error|overloaded|timeout|unavailable/.test(code))
153
+ return 'OpenAI is overloaded. Try again shortly.';
154
+ if (/content_policy|safety|refus/.test(code))
155
+ return 'OpenAI declined to answer this message.';
156
+ return `OpenAI could not answer (${code || 'no reason given'}). It is logged; try again or pick another model.`;
157
+ }
136
158
  const isApiError = (err) => err instanceof Anthropic.APIError || err instanceof OpenAI.APIError;
137
159
  export class Assistant {
138
160
  o;
@@ -1112,6 +1134,22 @@ export class Assistant {
1112
1134
  // an empty `output`, its items having come only as `response.output_item.done`.
1113
1135
  const seen = {};
1114
1136
  const items = [];
1137
+ // An error OpenAI sends inside the stream: logged in full, shown as a plain sentence.
1138
+ const streamFailure = (er) => {
1139
+ if (partial)
1140
+ sink.push({ kind: 'text', text: partial });
1141
+ console.error(JSON.stringify({
1142
+ event: 'assistant_openai_error',
1143
+ model: session.model.id,
1144
+ plan,
1145
+ code: er.code ?? null,
1146
+ type: er.type ?? null,
1147
+ param: er.param ?? null,
1148
+ message: er.message?.slice(0, 500) ?? null,
1149
+ events: seen,
1150
+ }));
1151
+ return new AssistantError(openaiStreamError(er.code || er.type || '', plan), 'upstream');
1152
+ };
1115
1153
  const res = await (async () => {
1116
1154
  const stream = await client.responses.create({
1117
1155
  model: session.model.id,
@@ -1139,7 +1177,8 @@ export class Assistant {
1139
1177
  done = e.response;
1140
1178
  }
1141
1179
  else if (e.type === 'error') {
1142
- throw new AssistantError('OpenAI could not finish the answer.', 'upstream');
1180
+ const er = (e.error ?? e);
1181
+ throw streamFailure(er);
1143
1182
  }
1144
1183
  }
1145
1184
  if (!done)
@@ -1148,6 +1187,15 @@ export class Assistant {
1148
1187
  })().catch((err) => {
1149
1188
  if (signal?.aborted && partial)
1150
1189
  sink.push({ kind: 'text', text: partial });
1190
+ // The SDK throws a stream's `error` event as an APIError with no HTTP status.
1191
+ if (err instanceof OpenAI.APIError && err.status === undefined) {
1192
+ const er = (err.error ?? {});
1193
+ throw streamFailure({
1194
+ ...er,
1195
+ code: er.code ?? err.code ?? undefined,
1196
+ message: er.message ?? err.message,
1197
+ });
1198
+ }
1151
1199
  throw err;
1152
1200
  });
1153
1201
  if (res.status === 'failed') {
@@ -1162,9 +1210,7 @@ export class Assistant {
1162
1210
  message: res.error?.message?.slice(0, 300) ?? null,
1163
1211
  events: seen,
1164
1212
  }));
1165
- throw new AssistantError(/usage_limit/.test(code)
1166
- ? 'Your ChatGPT plan reached its usage limit for now: try again later, or pick another model.'
1167
- : 'OpenAI could not finish the answer.', 'upstream');
1213
+ throw new AssistantError(openaiStreamError(code, plan), 'upstream');
1168
1214
  }
1169
1215
  const u = res.usage;
1170
1216
  if (u) {
@@ -1198,6 +1244,7 @@ export class Assistant {
1198
1244
  usage: res.usage ?? null,
1199
1245
  events: seen,
1200
1246
  output: output.map((b) => b.type),
1247
+ parts: output.flatMap((b) => b.type === 'message' ? b.content.map((c) => c.type) : []),
1201
1248
  }));
1202
1249
  if (!answer)
1203
1250
  throw new AssistantError(res.incomplete_details?.reason
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ai",
3
- "version": "0.1.97",
3
+ "version": "0.1.99",
4
4
  "description": "Lumen's assistant and agent parts: a chat assistant's server (Claude tiers, tools over MCP, the web, hand off, spend caps), serving an MCP server from a Worker, read-only tool sets and metered tool calls.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -41,8 +41,8 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@anthropic-ai/sdk": "0.133.0",
44
- "@columenlabs/lumen-ops": "0.1.97",
45
- "@columenlabs/lumen-ui-types": "0.1.97",
44
+ "@columenlabs/lumen-ops": "0.1.99",
45
+ "@columenlabs/lumen-ui-types": "0.1.99",
46
46
  "openai": "7.32.0",
47
47
  "zod": "^4.3.6"
48
48
  },