@columenlabs/lumen-ai 0.1.96 → 0.1.97

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.
@@ -1108,6 +1108,10 @@ export class Assistant {
1108
1108
  // the SDK does not know and its accumulator throws on them. The final response rides on the
1109
1109
  // completed, incomplete or failed event; every other event but text deltas is skipped.
1110
1110
  let partial = '';
1111
+ // Event types seen and the items finished one by one: a plan sign-in's final event may carry
1112
+ // an empty `output`, its items having come only as `response.output_item.done`.
1113
+ const seen = {};
1114
+ const items = [];
1111
1115
  const res = await (async () => {
1112
1116
  const stream = await client.responses.create({
1113
1117
  model: session.model.id,
@@ -1122,6 +1126,9 @@ export class Assistant {
1122
1126
  }, signal ? { signal } : {});
1123
1127
  let done;
1124
1128
  for await (const e of stream) {
1129
+ seen[e.type] = (seen[e.type] ?? 0) + 1;
1130
+ if (e.type === 'response.output_item.done' && e.item)
1131
+ items.push(e.item);
1125
1132
  if (e.type === 'response.output_text.delta' && typeof e.delta === 'string') {
1126
1133
  partial += e.delta;
1127
1134
  emit('text', { delta: e.delta });
@@ -1147,6 +1154,14 @@ export class Assistant {
1147
1154
  if (partial)
1148
1155
  sink.push({ kind: 'text', text: partial });
1149
1156
  const code = res.error?.code ?? '';
1157
+ console.error(JSON.stringify({
1158
+ event: 'assistant_openai_failed',
1159
+ model: session.model.id,
1160
+ plan,
1161
+ code,
1162
+ message: res.error?.message?.slice(0, 300) ?? null,
1163
+ events: seen,
1164
+ }));
1150
1165
  throw new AssistantError(/usage_limit/.test(code)
1151
1166
  ? 'Your ChatGPT plan reached its usage limit for now: try again later, or pick another model.'
1152
1167
  : 'OpenAI could not finish the answer.', 'upstream');
@@ -1164,14 +1179,36 @@ export class Assistant {
1164
1179
  o.context?.(u.input_tokens + u.output_tokens);
1165
1180
  }
1166
1181
  o.called();
1167
- const cut = res.incomplete_details?.reason === 'max_output_tokens' ? ' …(cut)' : '';
1168
- if (cut)
1169
- emit('text', { delta: cut });
1182
+ const output = (res.output?.length ? res.output : items);
1183
+ res.output = output;
1170
1184
  // Raw events carry no `output_text`: the words are the output messages' text parts.
1171
- const words = res.output
1185
+ const words = output
1172
1186
  .flatMap((b) => (b.type === 'message' ? b.content : []))
1173
1187
  .map((c) => (c.type === 'output_text' ? c.text : ''))
1174
- .join('');
1188
+ .join('') || partial;
1189
+ const hasCall = output.some((b) => b.type === 'function_call');
1190
+ if (!words && !hasCall) {
1191
+ // Nothing to show: say why instead of an empty or bare "cut" answer, and log the shape.
1192
+ console.error(JSON.stringify({
1193
+ event: 'assistant_openai_empty',
1194
+ model: session.model.id,
1195
+ plan,
1196
+ status: res.status,
1197
+ incomplete: res.incomplete_details ?? null,
1198
+ usage: res.usage ?? null,
1199
+ events: seen,
1200
+ output: output.map((b) => b.type),
1201
+ }));
1202
+ if (!answer)
1203
+ throw new AssistantError(res.incomplete_details?.reason
1204
+ ? `OpenAI stopped before answering (${res.incomplete_details.reason}).`
1205
+ : 'OpenAI sent no answer.', 'upstream');
1206
+ }
1207
+ const cut = res.incomplete_details?.reason === 'max_output_tokens' && (words || hasCall)
1208
+ ? ' …(cut)'
1209
+ : '';
1210
+ if (cut)
1211
+ emit('text', { delta: cut });
1175
1212
  const text = words + cut;
1176
1213
  if (text) {
1177
1214
  answer += (answer ? '\n\n' : '') + text;
@@ -1179,8 +1216,8 @@ export class Assistant {
1179
1216
  emit('text_end', {});
1180
1217
  }
1181
1218
  // Reasoning, words and calls go back as they came, so the model keeps its own thread.
1182
- input.push(...res.output);
1183
- const calls = res.output.filter((b) => b.type === 'function_call');
1219
+ input.push(...output);
1220
+ const calls = output.filter((b) => b.type === 'function_call');
1184
1221
  if (calls.length === 0)
1185
1222
  break;
1186
1223
  const results = await Promise.all(calls.map((c) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ai",
3
- "version": "0.1.96",
3
+ "version": "0.1.97",
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.96",
45
- "@columenlabs/lumen-ui-types": "0.1.96",
44
+ "@columenlabs/lumen-ops": "0.1.97",
45
+ "@columenlabs/lumen-ui-types": "0.1.97",
46
46
  "openai": "7.32.0",
47
47
  "zod": "^4.3.6"
48
48
  },