@keystrokehq/keystroke 0.1.36 → 0.1.37

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.
package/dist/agent.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_dist = require("./dist-BkxEtBJy.cjs");
3
3
  const require_dist$1 = require("./dist-Cy042cqb.cjs");
4
- const require_dist$2 = require("./dist-Di6PqKAu.cjs");
4
+ const require_dist$2 = require("./dist-DlIUXcOB.cjs");
5
5
  const require_dist$3 = require("./dist-BBGCAuTi.cjs");
6
6
  let zod = require("zod");
7
7
  let node_async_hooks = require("node:async_hooks");
@@ -1171,6 +1171,85 @@ function llmUsageFromLanguageModelUsage(usage, resolved, options) {
1171
1171
  }
1172
1172
  };
1173
1173
  }
1174
+ /** Synthetic tool name for structured output when responseFormat is unreliable. */
1175
+ const STRUCTURED_OUTPUT_SUBMIT_TOOL_NAME = "submit_structured_output";
1176
+ const SUBMIT_TOOL_VENDORS = new Set([
1177
+ "zai",
1178
+ "minimax",
1179
+ "alibaba",
1180
+ "deepseek"
1181
+ ]);
1182
+ function structuredOutputSubmitToolVendor(modelId) {
1183
+ const { vendor } = splitAgentModelId(modelId);
1184
+ return SUBMIT_TOOL_VENDORS.has(vendor);
1185
+ }
1186
+ /**
1187
+ * Use a submit tool instead of responseFormat.
1188
+ *
1189
+ * - Agent + tools: z-ai GLM only (native json mode works for schema-only agent calls).
1190
+ * - promptLlm: z-ai, minimax, alibaba, and deepseek — no gateway endpoint exposes reliable
1191
+ * response_format, so schema-only `generateObject` fails validation.
1192
+ */
1193
+ function usesStructuredOutputSubmitTool(args) {
1194
+ if (!args.structuredOutput || !structuredOutputSubmitToolVendor(args.modelId)) return false;
1195
+ if (args.promptLlm) return true;
1196
+ const { vendor } = splitAgentModelId(args.modelId);
1197
+ return (vendor === "zai" || vendor === "alibaba") && (args.hasTools ?? false);
1198
+ }
1199
+ function appendStructuredOutputSubmitTool(tools, schema) {
1200
+ return {
1201
+ ...tools,
1202
+ [STRUCTURED_OUTPUT_SUBMIT_TOOL_NAME]: require_dist$2.tool({
1203
+ description: "Submit the final structured result after completing any required tool calls. Include every required schema field.",
1204
+ inputSchema: schema,
1205
+ execute: async (input) => input
1206
+ })
1207
+ };
1208
+ }
1209
+ function resolveStructuredOutputStopWhen(args) {
1210
+ if (!usesStructuredOutputSubmitTool(args)) return;
1211
+ return require_dist$2.hasToolCall(STRUCTURED_OUTPUT_SUBMIT_TOOL_NAME);
1212
+ }
1213
+ function extractStructuredOutputFromSubmitTool(args) {
1214
+ for (let index = args.steps.length - 1; index >= 0; index -= 1) {
1215
+ const step = args.steps[index];
1216
+ if (!step) continue;
1217
+ for (const toolCall of step.toolCalls) if (toolCall.toolName === "submit_structured_output") return args.schema.parse(toolCall.input);
1218
+ }
1219
+ }
1220
+ /**
1221
+ * Alibaba rejects `response_format: json_object` unless messages contain the word "json".
1222
+ * Applies when structured output is enabled (including tool-loop steps that carry responseFormat).
1223
+ */
1224
+ function augmentInstructionsForStructuredOutput(args) {
1225
+ if (!args.structuredOutput) return args.instructions;
1226
+ const { vendor } = splitAgentModelId(args.modelId);
1227
+ let instructions = args.instructions;
1228
+ const submitTool = usesStructuredOutputSubmitTool({
1229
+ modelId: args.modelId,
1230
+ structuredOutput: args.structuredOutput,
1231
+ hasTools: args.hasTools ?? false,
1232
+ promptLlm: args.promptLlm
1233
+ });
1234
+ if (vendor === "alibaba" && !/\bjson\b/i.test(instructions)) instructions = `${instructions}\n\nWhen returning structured data, output valid json.`;
1235
+ if (submitTool) if (args.hasTools) instructions = `${instructions}\n\nCall the available tools first, then call ${STRUCTURED_OUTPUT_SUBMIT_TOOL_NAME} with the final result matching the schema. Do not return prose or raw json as the final answer.${vendor === "alibaba" ? " Do not return tool arguments or guesses as the final schema." : ""}`;
1236
+ else instructions = `${instructions}\n\nCall ${STRUCTURED_OUTPUT_SUBMIT_TOOL_NAME} with the final result matching the schema. Do not return prose or raw json as the final answer.`;
1237
+ else if (vendor === "zai") instructions = `${instructions}\n\nReturn the final answer as raw json matching the schema with every required field. Do not use markdown, bold, or prose in the structured response.`;
1238
+ return instructions;
1239
+ }
1240
+ function parseRepairedStructuredOutput(args) {
1241
+ const trimmed = args.text.trim();
1242
+ const candidates = [
1243
+ trimmed,
1244
+ trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i)?.[1]?.trim(),
1245
+ trimmed.match(/\{[\s\S]*\}/)?.[0]
1246
+ ].filter((value) => Boolean(value));
1247
+ for (const candidate of candidates) try {
1248
+ return args.schema.parse(JSON.parse(candidate));
1249
+ } catch {
1250
+ continue;
1251
+ }
1252
+ }
1174
1253
  /**
1175
1254
  * Baked-in AI SDK `providerOptions` a model needs for structured output.
1176
1255
  *
@@ -1190,6 +1269,70 @@ function resolveProviderOptions(args) {
1190
1269
  const { vendor } = splitAgentModelId(args.modelId);
1191
1270
  if (vendor === "anthropic") return { anthropic: { structuredOutputMode: "outputFormat" } };
1192
1271
  }
1272
+ /**
1273
+ * Wrap gateway models that need middleware for reliable structured output parsing.
1274
+ *
1275
+ * z-ai GLM models often wrap JSON in markdown fences; AI SDK's `extractJsonMiddleware` strips them.
1276
+ * See vercel/ai middleware docs and vercel/ai#12491.
1277
+ */
1278
+ function wrapLanguageModelForStructuredOutput(args) {
1279
+ if (!args.structuredOutput) return args.languageModel;
1280
+ if (usesStructuredOutputSubmitTool({
1281
+ modelId: args.modelId,
1282
+ structuredOutput: args.structuredOutput,
1283
+ hasTools: args.hasTools ?? false
1284
+ })) return args.languageModel;
1285
+ const { vendor } = splitAgentModelId(args.modelId);
1286
+ if (vendor !== "zai") return args.languageModel;
1287
+ return require_dist$2.wrapLanguageModel({
1288
+ model: args.languageModel,
1289
+ middleware: require_dist$2.extractJsonMiddleware()
1290
+ });
1291
+ }
1292
+ function needsStructuredOutputToolGuard(modelId) {
1293
+ const { vendor, directId } = splitAgentModelId(modelId);
1294
+ if (vendor === "anthropic") return !directId.includes("haiku");
1295
+ return false;
1296
+ }
1297
+ /**
1298
+ * Vendor-scoped guard for structured output + tools (BUS-2823).
1299
+ *
1300
+ * Anthropic Sonnet-class models may satisfy structured output on step 1 without calling tools;
1301
+ * AI SDK's tool loop then stops. Force `toolChoice: "required"` until the first tool result,
1302
+ * then release for the final structured step.
1303
+ *
1304
+ * Do not generalize: Gemini rejects forced tool choice + schema; Anthropic Haiku rejects forced
1305
+ * tool choice + thinking. z-ai GLM uses {@link usesStructuredOutputSubmitTool} instead.
1306
+ */
1307
+ function resolveStructuredOutputPrepareStep(args) {
1308
+ if (!args.structuredOutput || !args.hasTools) return;
1309
+ if (!needsStructuredOutputToolGuard(args.modelId)) return;
1310
+ return ({ steps }) => {
1311
+ if (steps.some((step) => step.toolResults.length > 0)) return {};
1312
+ return { toolChoice: "required" };
1313
+ };
1314
+ }
1315
+ /**
1316
+ * Alibaba and minimax thinking mode conflicts with forced tool_choice on structured promptLlm calls.
1317
+ */
1318
+ function resolveReasoningForStructuredOutputPromptLlm(args) {
1319
+ if (!usesStructuredOutputSubmitTool({
1320
+ modelId: args.modelId,
1321
+ structuredOutput: args.structuredOutput,
1322
+ promptLlm: true
1323
+ })) return args.thinkingLevel;
1324
+ const { vendor } = splitAgentModelId(args.modelId);
1325
+ if (vendor === "alibaba" || vendor === "minimax") return "none";
1326
+ return args.thinkingLevel;
1327
+ }
1328
+ /** Alibaba may require the word "json" in user messages when response_format is enabled. */
1329
+ function augmentInputForStructuredOutput(args) {
1330
+ if (!args.structuredOutput) return args.input;
1331
+ const { vendor } = splitAgentModelId(args.modelId);
1332
+ if (vendor !== "alibaba") return args.input;
1333
+ if (/\bjson\b/i.test(args.input)) return args.input;
1334
+ return `${args.input}\n\nReturn the final answer as json matching the schema.`;
1335
+ }
1193
1336
  /** Plain-text tool error for UI streams, model replay, and JSON persistence. */
1194
1337
  function getToolExecutionErrorMessage(error) {
1195
1338
  if (error instanceof Error) return error.message;
@@ -1218,7 +1361,11 @@ async function streamAgentPrompt(config, input, options) {
1218
1361
  const userMessage = {
1219
1362
  id: require_dist$2.generateId(),
1220
1363
  role: "user",
1221
- parts: buildUserMessageParts(input, config.files)
1364
+ parts: buildUserMessageParts(augmentInputForStructuredOutput({
1365
+ input,
1366
+ modelId: config.resolvedModel.modelId,
1367
+ structuredOutput: Boolean(options.outputSchema)
1368
+ }), config.files)
1222
1369
  };
1223
1370
  const priorMessages = [...config.messages, userMessage];
1224
1371
  const newMessages = [userMessage];
@@ -1233,25 +1380,55 @@ async function streamAgentPrompt(config, input, options) {
1233
1380
  });
1234
1381
  try {
1235
1382
  if (config.files?.length && !config.experimentalDownload) throw new Error("Chat attachments require worker storage (STORAGE_* / KEYSTROKE_WORKSPACES_BUCKET)");
1236
- const aiTools = gateToolSetMedia({
1383
+ let aiTools = gateToolSetMedia({
1237
1384
  ...agentToolsToAiToolSet(config.tools),
1238
1385
  ...config.mcpToolSet
1239
1386
  }, config.resolvedModel.capabilities);
1387
+ const structuredOutput = Boolean(options.outputSchema);
1388
+ const hasTools = Object.keys(aiTools).length > 0;
1389
+ const submitToolOutput = usesStructuredOutputSubmitTool({
1390
+ modelId: config.resolvedModel.modelId,
1391
+ structuredOutput,
1392
+ hasTools
1393
+ });
1394
+ if (submitToolOutput && options.outputSchema) aiTools = appendStructuredOutputSubmitTool(aiTools, options.outputSchema);
1240
1395
  let messagesForModel = await applyCapabilityPlaceholderToMessages(priorMessages, config.resolvedModel.capabilities);
1241
1396
  if (config.experimentalDownload) messagesForModel = await hydrateChatAttachmentMessages(messagesForModel, config.experimentalDownload);
1242
1397
  const modelMessages = await require_dist$2.convertToModelMessages(messagesForModel);
1243
1398
  const providerOptions = resolveProviderOptions({
1244
1399
  modelId: config.resolvedModel.modelId,
1245
- structuredOutput: Boolean(options.outputSchema)
1400
+ structuredOutput
1401
+ });
1402
+ const prepareStep = resolveStructuredOutputPrepareStep({
1403
+ modelId: config.resolvedModel.modelId,
1404
+ structuredOutput,
1405
+ hasTools
1406
+ });
1407
+ const stopWhen = resolveStructuredOutputStopWhen({
1408
+ modelId: config.resolvedModel.modelId,
1409
+ structuredOutput,
1410
+ hasTools
1246
1411
  });
1247
1412
  const streamResult = await new require_dist$2.ToolLoopAgent({
1248
- model: config.resolvedModel.languageModel,
1249
- instructions: config.systemPrompt,
1413
+ model: wrapLanguageModelForStructuredOutput({
1414
+ languageModel: config.resolvedModel.languageModel,
1415
+ modelId: config.resolvedModel.modelId,
1416
+ structuredOutput,
1417
+ hasTools
1418
+ }),
1419
+ instructions: augmentInstructionsForStructuredOutput({
1420
+ instructions: config.systemPrompt,
1421
+ modelId: config.resolvedModel.modelId,
1422
+ structuredOutput,
1423
+ hasTools
1424
+ }),
1250
1425
  tools: aiTools,
1251
1426
  reasoning: config.thinkingLevel,
1252
1427
  ...config.experimentalDownload ? { experimental_download: config.experimentalDownload } : {},
1253
- ...options.outputSchema ? { output: require_dist$2.output_exports.object({ schema: options.outputSchema }) } : {},
1254
- ...providerOptions ? { providerOptions } : {}
1428
+ ...!submitToolOutput && options.outputSchema ? { output: require_dist$2.output_exports.object({ schema: options.outputSchema }) } : {},
1429
+ ...providerOptions ? { providerOptions } : {},
1430
+ ...prepareStep ? { prepareStep } : {},
1431
+ ...stopWhen ? { stopWhen } : {}
1255
1432
  }).stream({
1256
1433
  messages: modelMessages,
1257
1434
  abortSignal: options.signal,
@@ -1318,7 +1495,22 @@ async function streamAgentPrompt(config, input, options) {
1318
1495
  });
1319
1496
  }
1320
1497
  text = await streamResult.text;
1321
- if (options.outputSchema) output = await streamResult.output;
1498
+ if (options.outputSchema) if (submitToolOutput) {
1499
+ output = extractStructuredOutputFromSubmitTool({
1500
+ steps: await streamResult.steps,
1501
+ schema: options.outputSchema
1502
+ });
1503
+ if (output === void 0) throw new Error(`Structured output was not submitted via ${STRUCTURED_OUTPUT_SUBMIT_TOOL_NAME}`);
1504
+ } else try {
1505
+ output = await streamResult.output;
1506
+ } catch (caught) {
1507
+ const repaired = parseRepairedStructuredOutput({
1508
+ text,
1509
+ schema: options.outputSchema
1510
+ });
1511
+ if (repaired !== void 0) output = repaired;
1512
+ else throw caught;
1513
+ }
1322
1514
  await emit({
1323
1515
  type: "agent_end",
1324
1516
  messages: newMessages
@@ -2400,17 +2592,77 @@ async function runPromptText(opts, hooks) {
2400
2592
  await recordUsage(resolved, result.usage, hooks, result.response?.headers);
2401
2593
  return result.text;
2402
2594
  }
2595
+ async function runPromptObjectViaSubmitTool(opts, hooks) {
2596
+ const schema = opts.outputSchema;
2597
+ if (!schema) throw new Error("outputSchema is required for structured LLM output");
2598
+ const resolved = await resolvePromptModel(opts.model);
2599
+ const providerOptions = resolveProviderOptions({
2600
+ modelId: resolved.modelId,
2601
+ structuredOutput: true
2602
+ });
2603
+ const tools = appendStructuredOutputSubmitTool({}, schema);
2604
+ const system = augmentInstructionsForStructuredOutput({
2605
+ instructions: opts.system ?? "",
2606
+ modelId: resolved.modelId,
2607
+ structuredOutput: true,
2608
+ promptLlm: true
2609
+ });
2610
+ const prompt = augmentInputForStructuredOutput({
2611
+ input: opts.prompt,
2612
+ modelId: resolved.modelId,
2613
+ structuredOutput: true
2614
+ });
2615
+ const thinkingLevel = resolveReasoningForStructuredOutputPromptLlm({
2616
+ modelId: resolved.modelId,
2617
+ thinkingLevel: opts.thinkingLevel,
2618
+ structuredOutput: true
2619
+ });
2620
+ const result = await require_dist$2.generateText({
2621
+ model: resolved.languageModel,
2622
+ ...system.trim() ? { system } : {},
2623
+ prompt,
2624
+ tools,
2625
+ toolChoice: "required",
2626
+ stopWhen: require_dist$2.hasToolCall(STRUCTURED_OUTPUT_SUBMIT_TOOL_NAME),
2627
+ ...opts.temperature !== void 0 ? { temperature: opts.temperature } : {},
2628
+ ...opts.maxTokens !== void 0 ? { maxOutputTokens: opts.maxTokens } : {},
2629
+ ...reasoningCallOption(thinkingLevel),
2630
+ ...providerOptions ? { providerOptions } : {}
2631
+ });
2632
+ await recordUsage(resolved, result.usage, hooks, result.response?.headers);
2633
+ const fromSteps = extractStructuredOutputFromSubmitTool({
2634
+ steps: await result.steps,
2635
+ schema
2636
+ });
2637
+ if (fromSteps !== void 0) return fromSteps;
2638
+ throw new Error(`Structured output was not submitted via ${STRUCTURED_OUTPUT_SUBMIT_TOOL_NAME}`);
2639
+ }
2403
2640
  async function runPromptObject(opts, hooks) {
2404
2641
  const schema = opts.outputSchema;
2405
2642
  if (!schema) throw new Error("outputSchema is required for structured LLM output");
2406
2643
  const resolved = await resolvePromptModel(opts.model);
2644
+ if (usesStructuredOutputSubmitTool({
2645
+ modelId: resolved.modelId,
2646
+ structuredOutput: true,
2647
+ promptLlm: true
2648
+ })) return runPromptObjectViaSubmitTool(opts, hooks);
2407
2649
  const providerOptions = resolveProviderOptions({
2408
2650
  modelId: resolved.modelId,
2409
2651
  structuredOutput: true
2410
2652
  });
2653
+ const languageModel = wrapLanguageModelForStructuredOutput({
2654
+ languageModel: resolved.languageModel,
2655
+ modelId: resolved.modelId,
2656
+ structuredOutput: true
2657
+ });
2658
+ const system = augmentInstructionsForStructuredOutput({
2659
+ instructions: opts.system ?? "",
2660
+ modelId: resolved.modelId,
2661
+ structuredOutput: true
2662
+ });
2411
2663
  const result = await require_dist$2.generateObject({
2412
- model: resolved.languageModel,
2413
- system: opts.system,
2664
+ model: languageModel,
2665
+ ...system.trim() ? { system } : {},
2414
2666
  prompt: opts.prompt,
2415
2667
  schema,
2416
2668
  ...opts.temperature !== void 0 ? { temperature: opts.temperature } : {},