@ai-sdk/harness-deepagents 1.0.72 → 1.0.74

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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @ai-sdk/harness-deepagents
2
2
 
3
+ ## 1.0.74
4
+
5
+ ### Patch Changes
6
+
7
+ - @ai-sdk/harness@1.0.74
8
+
9
+ ## 1.0.73
10
+
11
+ ### Patch Changes
12
+
13
+ - 62a9c2a: feat(harness): add support for structured output to `HarnessAgent` via `output` property
14
+ - Updated dependencies [62a9c2a]
15
+ - Updated dependencies [d25cae2]
16
+ - @ai-sdk/harness@1.0.73
17
+
3
18
  ## 1.0.72
4
19
 
5
20
  ### Patch Changes
@@ -57,7 +57,6 @@ async function runBridge(options) {
57
57
  let currentBoundPort = 0;
58
58
  let currentTurnState = "init";
59
59
  let activeSocket;
60
- let activeSocketReadyForLiveEvents = false;
61
60
  let isFirstTurn = true;
62
61
  let turnAbort;
63
62
  let currentUserMessages;
@@ -141,14 +140,6 @@ async function runBridge(options) {
141
140
  } catch {
142
141
  }
143
142
  };
144
- const sendControl = (msg) => {
145
- if (activeSocket?.readyState === WS_OPEN) {
146
- try {
147
- activeSocket.send(JSON.stringify(msg));
148
- } catch {
149
- }
150
- }
151
- };
152
143
  const emit = (event) => {
153
144
  const seq = ++seqCounter;
154
145
  const line = JSON.stringify({ ...event, seq });
@@ -156,7 +147,7 @@ async function runBridge(options) {
156
147
  diskBuffer += `${line}
157
148
  `;
158
149
  scheduleEventFlush();
159
- if (activeSocketReadyForLiveEvents && activeSocket?.readyState === WS_OPEN) {
150
+ if (activeSocket?.readyState === WS_OPEN) {
160
151
  try {
161
152
  activeSocket.send(line);
162
153
  } catch {
@@ -259,8 +250,7 @@ async function runBridge(options) {
259
250
  const handleInbound = async (msg, ws) => {
260
251
  switch (msg.type) {
261
252
  case "start": {
262
- if (activeSocket !== ws) return;
263
- activeSocketReadyForLiveEvents = true;
253
+ activeSocket = ws;
264
254
  const firstTurn = isFirstTurn;
265
255
  isFirstTurn = false;
266
256
  eventLog = [];
@@ -340,9 +330,8 @@ async function runBridge(options) {
340
330
  turnAbort?.abort();
341
331
  return;
342
332
  case "resume":
343
- if (activeSocket !== ws) return;
333
+ activeSocket = ws;
344
334
  replay(ws, msg.lastSeenEventId);
345
- activeSocketReadyForLiveEvents = true;
346
335
  return;
347
336
  case "destroy":
348
337
  currentTurnState = "done";
@@ -354,7 +343,7 @@ async function runBridge(options) {
354
343
  currentTurnState = "done";
355
344
  void writeBridgeMeta("done");
356
345
  const data = await onStop?.() ?? {};
357
- sendControl({ type: "bridge-stop", data });
346
+ sendControl(ws, { type: "bridge-stop", data });
358
347
  drainThenExit(ws, 1e3, "stop");
359
348
  return;
360
349
  }
@@ -407,9 +396,7 @@ async function runBridge(options) {
407
396
  ws.close(1008, "unauthorized");
408
397
  return;
409
398
  }
410
- activeSocket = ws;
411
- activeSocketReadyForLiveEvents = false;
412
- sendControl({
399
+ sendControl(ws, {
413
400
  type: "bridge-hello",
414
401
  state: currentTurnState,
415
402
  lastSeq: seqCounter
@@ -420,7 +407,7 @@ async function runBridge(options) {
420
407
  const text = typeof raw === "string" ? raw : Buffer.from(raw).toString("utf8");
421
408
  parsed = JSON.parse(text);
422
409
  } catch (err) {
423
- sendControl({
410
+ sendControl(ws, {
424
411
  type: "error",
425
412
  error: `protocol parse error: ${err.message}`
426
413
  });
@@ -431,7 +418,6 @@ async function runBridge(options) {
431
418
  ws.on("close", () => {
432
419
  if (activeSocket === ws) {
433
420
  activeSocket = void 0;
434
- activeSocketReadyForLiveEvents = false;
435
421
  }
436
422
  });
437
423
  ws.on("error", () => {
@@ -458,6 +444,14 @@ async function runBridge(options) {
458
444
  })
459
445
  };
460
446
  }
447
+ function sendControl(socket, message) {
448
+ if (socket?.readyState === WS_OPEN) {
449
+ try {
450
+ socket.send(JSON.stringify(message));
451
+ } catch {
452
+ }
453
+ }
454
+ }
461
455
  function serialiseError(err) {
462
456
  if (err instanceof Error) {
463
457
  return { name: err.name, message: err.message, stack: err.stack };
@@ -569,11 +563,149 @@ import { tool } from "@langchain/core/tools";
569
563
 
570
564
  // ../../node_modules/.pnpm/langchain@1.5.3_@langchain+core@1.2.3_@opentelemetry+api@1.9.1_@opentelemetry+exporter-_a0dbd4fc7a66247dcd0e899a4ff3ea34/node_modules/langchain/dist/agents/errors.js
571
565
  import { isGraphBubbleUp } from "@langchain/langgraph";
566
+ var StructuredOutputParsingError = class extends Error {
567
+ toolName;
568
+ errors;
569
+ constructor(toolName, errors) {
570
+ super(`Failed to parse structured output for tool '${toolName}':${errors.map((e) => `
571
+ - ${e}`).join("")}.`);
572
+ this.toolName = toolName;
573
+ this.errors = errors;
574
+ }
575
+ };
576
+
577
+ // ../../node_modules/.pnpm/langchain@1.5.3_@langchain+core@1.2.3_@opentelemetry+api@1.9.1_@opentelemetry+exporter-_a0dbd4fc7a66247dcd0e899a4ff3ea34/node_modules/langchain/dist/agents/model.js
578
+ function isBaseChatModel(model) {
579
+ return "invoke" in model && typeof model.invoke === "function" && "_streamResponseChunks" in model;
580
+ }
572
581
 
573
582
  // ../../node_modules/.pnpm/langchain@1.5.3_@langchain+core@1.2.3_@opentelemetry+api@1.9.1_@opentelemetry+exporter-_a0dbd4fc7a66247dcd0e899a4ff3ea34/node_modules/langchain/dist/agents/responses.js
574
583
  import { isInteropZodObject, isInteropZodSchema } from "@langchain/core/utils/types";
575
584
  import { Validator, toJsonSchema } from "@langchain/core/utils/json_schema";
576
585
  import { isSerializableSchema } from "@langchain/core/utils/standard_schema";
586
+ var PROVIDER_STRATEGY_DEFAULT_STRICT = true;
587
+ var bindingIdentifier = 0;
588
+ var ToolStrategy = class ToolStrategy2 {
589
+ constructor(schema, tool4, options) {
590
+ this.schema = schema;
591
+ this.tool = tool4;
592
+ this.options = options;
593
+ }
594
+ get name() {
595
+ return this.tool.function.name;
596
+ }
597
+ static fromSchema(schema, outputOptions) {
598
+ function getFunctionName(name) {
599
+ return name ?? `extract-${++bindingIdentifier}`;
600
+ }
601
+ if (isSerializableSchema(schema) || isInteropZodSchema(schema)) {
602
+ const asJsonSchema = toJsonSchema(schema);
603
+ return new ToolStrategy2(asJsonSchema, {
604
+ type: "function",
605
+ function: {
606
+ name: getFunctionName(asJsonSchema.title),
607
+ strict: false,
608
+ description: asJsonSchema.description ?? "Tool for extracting structured output from the model's response.",
609
+ parameters: asJsonSchema
610
+ }
611
+ }, outputOptions);
612
+ }
613
+ let functionDefinition;
614
+ if (typeof schema.name === "string" && typeof schema.parameters === "object" && schema.parameters != null) functionDefinition = schema;
615
+ else functionDefinition = {
616
+ name: getFunctionName(schema.title),
617
+ description: schema.description ?? "",
618
+ parameters: schema.schema || schema
619
+ };
620
+ return new ToolStrategy2(toJsonSchema(schema), {
621
+ type: "function",
622
+ function: functionDefinition
623
+ }, outputOptions);
624
+ }
625
+ /**
626
+ * Parse tool arguments according to the schema.
627
+ *
628
+ * @throws {StructuredOutputParsingError} if the response is not valid
629
+ * @param toolArgs - The arguments from the tool call
630
+ * @returns The parsed response according to the schema type
631
+ */
632
+ parse(toolArgs) {
633
+ const result = new Validator(this.schema).validate(toolArgs);
634
+ if (!result.valid) throw new StructuredOutputParsingError(this.name, result.errors.map((e) => e.error));
635
+ return toolArgs;
636
+ }
637
+ };
638
+ var ProviderStrategy = class ProviderStrategy2 {
639
+ _schemaType;
640
+ /**
641
+ * The schema to use for the provider strategy
642
+ */
643
+ schema;
644
+ /**
645
+ * Whether to use strict mode for the provider strategy
646
+ */
647
+ strict;
648
+ constructor(schemaOrOptions, strict) {
649
+ if ("schema" in schemaOrOptions && typeof schemaOrOptions.schema === "object" && schemaOrOptions.schema !== null && !("type" in schemaOrOptions)) {
650
+ const options = schemaOrOptions;
651
+ this.schema = options.schema;
652
+ this.strict = options.strict ?? PROVIDER_STRATEGY_DEFAULT_STRICT;
653
+ } else {
654
+ this.schema = schemaOrOptions;
655
+ this.strict = strict ?? PROVIDER_STRATEGY_DEFAULT_STRICT;
656
+ }
657
+ }
658
+ static fromSchema(schema, strict) {
659
+ return new ProviderStrategy2(toJsonSchema(schema), strict);
660
+ }
661
+ /**
662
+ * Parse tool arguments according to the schema. If the response is not valid, return undefined.
663
+ *
664
+ * @param response - The AI message response to parse
665
+ * @returns The parsed response according to the schema type
666
+ */
667
+ parse(response) {
668
+ let textContent;
669
+ if (typeof response.content === "string") textContent = response.content;
670
+ else if (Array.isArray(response.content)) {
671
+ for (const block of response.content) if (typeof block === "object" && block !== null && "type" in block && block.type === "text" && "text" in block && typeof block.text === "string") {
672
+ textContent = block.text;
673
+ break;
674
+ }
675
+ }
676
+ if (!textContent || textContent === "") return;
677
+ try {
678
+ const content = JSON.parse(textContent);
679
+ if (!new Validator(this.schema).validate(content).valid) return;
680
+ return content;
681
+ } catch {
682
+ }
683
+ }
684
+ };
685
+ function transformResponseFormat(responseFormat, options, model) {
686
+ if (!responseFormat) return [];
687
+ if (typeof responseFormat === "object" && responseFormat !== null && "__responseFormatUndefined" in responseFormat) return [];
688
+ if (Array.isArray(responseFormat)) {
689
+ if (responseFormat.every((item) => item instanceof ToolStrategy || item instanceof ProviderStrategy)) return responseFormat;
690
+ if (responseFormat.every((item) => isSerializableSchema(item))) return responseFormat.map((item) => ToolStrategy.fromSchema(item, options));
691
+ if (responseFormat.every((item) => isInteropZodObject(item))) return responseFormat.map((item) => ToolStrategy.fromSchema(item, options));
692
+ if (responseFormat.every((item) => typeof item === "object" && item !== null && !isInteropZodObject(item) && !isSerializableSchema(item))) return responseFormat.map((item) => ToolStrategy.fromSchema(item, options));
693
+ throw new Error("Invalid response format: list contains mixed types.\nAll items must be either InteropZodObject, Standard Schema, or plain JSON schema objects.");
694
+ }
695
+ if (responseFormat instanceof ToolStrategy || responseFormat instanceof ProviderStrategy) return [responseFormat];
696
+ const useProviderStrategy = hasSupportForJsonSchemaOutput(model);
697
+ if (isSerializableSchema(responseFormat)) return useProviderStrategy ? [ProviderStrategy.fromSchema(responseFormat)] : [ToolStrategy.fromSchema(responseFormat, options)];
698
+ if (isInteropZodObject(responseFormat)) return useProviderStrategy ? [ProviderStrategy.fromSchema(responseFormat)] : [ToolStrategy.fromSchema(responseFormat, options)];
699
+ if (typeof responseFormat === "object" && responseFormat !== null && "properties" in responseFormat) return useProviderStrategy ? [ProviderStrategy.fromSchema(responseFormat)] : [ToolStrategy.fromSchema(responseFormat, options)];
700
+ throw new Error(`Invalid response format: ${String(responseFormat)}`);
701
+ }
702
+ function toolStrategy(responseFormat, options) {
703
+ return transformResponseFormat(responseFormat, options);
704
+ }
705
+ function hasSupportForJsonSchemaOutput(model) {
706
+ if (!model || !isBaseChatModel(model) || !("profile" in model) || typeof model.profile !== "object" || !model.profile) return false;
707
+ return "structuredOutput" in model.profile && model.profile.structuredOutput === true;
708
+ }
577
709
 
578
710
  // ../../node_modules/.pnpm/langchain@1.5.3_@langchain+core@1.2.3_@opentelemetry+api@1.9.1_@opentelemetry+exporter-_a0dbd4fc7a66247dcd0e899a4ff3ea34/node_modules/langchain/dist/agents/middleware/utils.js
579
711
  import { AIMessage, ToolMessage } from "@langchain/core/messages";
@@ -1321,6 +1453,7 @@ function createEmitStreamEvent({
1321
1453
  configuredModel,
1322
1454
  hostToolNames,
1323
1455
  mcpToolNames: mcpToolNames2,
1456
+ structuredOutputToolNames = /* @__PURE__ */ new Set(),
1324
1457
  emit
1325
1458
  }) {
1326
1459
  return (event) => {
@@ -1391,6 +1524,7 @@ function createEmitStreamEvent({
1391
1524
  }
1392
1525
  } else if (kind === "on_tool_start") {
1393
1526
  const toolName = event.name ?? "unknown";
1527
+ if (structuredOutputToolNames.has(toolName)) return;
1394
1528
  const runId = event.run_id ?? "";
1395
1529
  if (!nested && !hostToolNames.has(toolName)) {
1396
1530
  const isMcpTool = mcpToolNames2.has(toolName);
@@ -1415,6 +1549,7 @@ function createEmitStreamEvent({
1415
1549
  }
1416
1550
  } else if (kind === "on_tool_end") {
1417
1551
  const toolName = event.name ?? "unknown";
1552
+ if (structuredOutputToolNames.has(toolName)) return;
1418
1553
  const runId = event.run_id ?? "";
1419
1554
  if (!nested && !hostToolNames.has(toolName)) {
1420
1555
  const dynamic = state.dynamicToolRunIds.delete(runId);
@@ -1732,6 +1867,16 @@ var agent;
1732
1867
  var currentTurn;
1733
1868
  var mcpClient;
1734
1869
  var mcpToolNames = /* @__PURE__ */ new Set();
1870
+ var currentResponseFormat;
1871
+ var responseFormatMiddleware = createMiddleware({
1872
+ name: "HarnessResponseFormat",
1873
+ wrapModelCall(request, handler) {
1874
+ return handler({
1875
+ ...request,
1876
+ ...currentResponseFormat == null ? {} : { responseFormat: currentResponseFormat }
1877
+ });
1878
+ }
1879
+ });
1735
1880
  function buildHostTools(toolSchemas) {
1736
1881
  return (toolSchemas ?? []).map(
1737
1882
  (schema) => tool3(
@@ -1759,6 +1904,7 @@ function buildHostTools(toolSchemas) {
1759
1904
  }
1760
1905
  async function runTurn(start, turn) {
1761
1906
  currentTurn = turn;
1907
+ currentResponseFormat = start.responseFormat?.type === "json" && start.responseFormat.schema != null ? toolStrategy(start.responseFormat.schema) : void 0;
1762
1908
  const emit = (event) => turn.emit(event);
1763
1909
  const interruptOn = buildInterruptOn(
1764
1910
  start.permissionMode,
@@ -1785,6 +1931,7 @@ async function runTurn(start, turn) {
1785
1931
  }
1786
1932
  );
1787
1933
  const middleware = [
1934
+ responseFormatMiddleware,
1788
1935
  ...reasoningMiddleware ? [reasoningMiddleware] : [],
1789
1936
  ...builtinToolFilteringMiddleware ? [builtinToolFilteringMiddleware] : []
1790
1937
  ];
@@ -1819,6 +1966,9 @@ async function runTurn(start, turn) {
1819
1966
  configuredModel: start.model,
1820
1967
  hostToolNames,
1821
1968
  mcpToolNames,
1969
+ structuredOutputToolNames: new Set(
1970
+ currentResponseFormat?.map((format) => format.name) ?? []
1971
+ ),
1822
1972
  emit
1823
1973
  });
1824
1974
  const config = {
@@ -1842,10 +1992,25 @@ async function runTurn(start, turn) {
1842
1992
  let resumeInput = {
1843
1993
  messages: [{ role: "user", content: start.prompt }]
1844
1994
  };
1995
+ let emittedStructuredOutput = false;
1845
1996
  while (true) {
1846
1997
  const stream = await agent.streamEvents(resumeInput, config);
1847
1998
  for await (const event of stream) {
1848
1999
  emitStreamEvent(event);
2000
+ const streamEvent = event;
2001
+ const namespace = streamEvent.metadata?.langgraph_checkpoint_ns ?? "";
2002
+ const output = streamEvent.data?.output;
2003
+ if (!emittedStructuredOutput && streamEvent.event === "on_chain_end" && !namespace.includes("|") && output?.structuredResponse !== void 0) {
2004
+ const id = `structured-output-${randomUUID2()}`;
2005
+ emit({ type: "text-start", id });
2006
+ emit({
2007
+ type: "text-delta",
2008
+ id,
2009
+ delta: JSON.stringify(output.structuredResponse)
2010
+ });
2011
+ emit({ type: "text-end", id });
2012
+ emittedStructuredOutput = true;
2013
+ }
1849
2014
  }
1850
2015
  const actionRequests = await readPendingApprovals();
1851
2016
  if (actionRequests.length === 0) break;