@keystrokehq/keystroke 0.1.36 → 0.1.38

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