@linnlabs/linnkit 0.8.0 → 0.9.0

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.
@@ -1198,7 +1198,6 @@ var Logger = class {
1198
1198
  constructor(moduleName) {
1199
1199
  this.moduleName = moduleName;
1200
1200
  }
1201
- moduleName;
1202
1201
  debug(message, data) {
1203
1202
  this.log(0 /* DEBUG */, "debug", message, data);
1204
1203
  }
@@ -1280,7 +1279,6 @@ var GraphExecutor = class {
1280
1279
  };
1281
1280
  this.telemetryPort = config.telemetryPort ?? noopTelemetry;
1282
1281
  }
1283
- checkpointer;
1284
1282
  nodes = /* @__PURE__ */ new Map();
1285
1283
  ephemeralLocals = /* @__PURE__ */ new Map();
1286
1284
  config;
@@ -4562,6 +4560,22 @@ var ToolNode = class {
4562
4560
  });
4563
4561
  }
4564
4562
  async run(state) {
4563
+ const events = [];
4564
+ while (true) {
4565
+ const result = await this.runNextPendingToolCall(state);
4566
+ if (Array.isArray(result.events) && result.events.length > 0) {
4567
+ events.push(...result.events);
4568
+ }
4569
+ if (result.kind === "route" && result.nextNodeId === "tool") {
4570
+ continue;
4571
+ }
4572
+ return {
4573
+ ...result,
4574
+ events
4575
+ };
4576
+ }
4577
+ }
4578
+ async runNextPendingToolCall(state) {
4565
4579
  const calls = state.local?.pendingToolCalls ?? [];
4566
4580
  const signalRaw = state.local?.signal;
4567
4581
  if (isAbortSignal(signalRaw) && signalRaw.aborted) {
@@ -4769,18 +4783,25 @@ var ToolNode = class {
4769
4783
  rawArguments: context.call.function?.arguments,
4770
4784
  parsedArguments: context.toolArgs
4771
4785
  });
4786
+ const remainingCalls = context.calls.slice(1);
4772
4787
  context.state.local = buildErrorLocalState({
4773
4788
  local: context.local,
4774
- remainingCalls: context.calls.slice(1),
4789
+ remainingCalls,
4775
4790
  conversationId: context.conversationId,
4776
4791
  turnId: context.turnId,
4777
4792
  runtimeEvents: context.bridge.getRuntimeEvents(),
4778
4793
  nextProtocolErrorCount: fuse.nextCount
4779
4794
  });
4780
- if (fuse.shouldFuse) {
4795
+ if (fuse.shouldFuse && remainingCalls.length === 0) {
4781
4796
  throw createToolProtocolFuseError(fuse.nextCount, context.exec.error);
4782
4797
  }
4783
- return { kind: "route", nextNodeId: "llm", events: context.bridge.getRuntimeEvents() };
4798
+ return {
4799
+ kind: "route",
4800
+ // 同一个 assistant.tool_calls batch 必须为每个 call 产出 tool_output。
4801
+ // 出错时也继续消费剩余 call,ToolNode.run 会在本节点内 drain 完 batch 再回 LLM。
4802
+ nextNodeId: remainingCalls.length > 0 ? "tool" : "llm",
4803
+ events: context.bridge.getRuntimeEvents()
4804
+ };
4784
4805
  }
4785
4806
  };
4786
4807
 
@@ -5276,18 +5297,18 @@ function createClassification(category, reason, suggestedDelay, extras) {
5276
5297
  }
5277
5298
  var ErrorClassifier = class {
5278
5299
  static classify(error, context) {
5279
- const isRecord21 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
5300
+ const isRecord22 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
5280
5301
  const baseMsg = (error.message || "").toLowerCase();
5281
5302
  const causeMsg = (() => {
5282
5303
  const cause = error.cause;
5283
5304
  if (typeof cause === "string") return cause.toLowerCase();
5284
5305
  if (cause instanceof Error) return (cause.message || "").toLowerCase();
5285
- if (isRecord21(cause) && typeof cause["message"] === "string") return String(cause["message"]).toLowerCase();
5306
+ if (isRecord22(cause) && typeof cause["message"] === "string") return String(cause["message"]).toLowerCase();
5286
5307
  return "";
5287
5308
  })();
5288
5309
  const causeCode = (() => {
5289
5310
  const cause = error.cause;
5290
- if (isRecord21(cause) && typeof cause["code"] === "string") return String(cause["code"]);
5311
+ if (isRecord22(cause) && typeof cause["code"] === "string") return String(cause["code"]);
5291
5312
  return "";
5292
5313
  })();
5293
5314
  const errorMessage = `${baseMsg} ${causeMsg}`.trim();
@@ -5963,6 +5984,67 @@ function assertToolCallsHaveValidJsonArguments(toolCalls) {
5963
5984
  }
5964
5985
  }
5965
5986
 
5987
+ // src/runtime-kernel/llm/reasoning-details.ts
5988
+ var mergeableTextFields = ["reasoning_content"];
5989
+ function isRecord18(value) {
5990
+ return !!value && typeof value === "object" && !Array.isArray(value);
5991
+ }
5992
+ function findMergeableTextField(detail) {
5993
+ if (!isRecord18(detail)) return void 0;
5994
+ if (typeof detail.provider !== "string") return void 0;
5995
+ if (typeof detail.type !== "string") return void 0;
5996
+ for (const field of mergeableTextFields) {
5997
+ if (typeof detail[field] === "string") {
5998
+ return field;
5999
+ }
6000
+ }
6001
+ return void 0;
6002
+ }
6003
+ function hasOnlyStableTextDetailFields(detail, textField) {
6004
+ const allowedKeys = /* @__PURE__ */ new Set(["provider", "type", textField]);
6005
+ return Object.keys(detail).every((key) => allowedKeys.has(key));
6006
+ }
6007
+ function canMergeTextDetails(previous, incoming) {
6008
+ if (!isRecord18(previous) || !isRecord18(incoming)) return false;
6009
+ const previousField = findMergeableTextField(previous);
6010
+ const incomingField = findMergeableTextField(incoming);
6011
+ if (!previousField || previousField !== incomingField) return false;
6012
+ return previous.provider === incoming.provider && previous.type === incoming.type && hasOnlyStableTextDetailFields(previous, previousField) && hasOnlyStableTextDetailFields(incoming, incomingField);
6013
+ }
6014
+ function mergeStreamingText(previous, incoming) {
6015
+ if (!previous) return incoming;
6016
+ if (!incoming) return previous;
6017
+ if (incoming.startsWith(previous)) {
6018
+ return incoming;
6019
+ }
6020
+ if (previous.endsWith(incoming)) {
6021
+ return previous;
6022
+ }
6023
+ return `${previous}${incoming}`;
6024
+ }
6025
+ function appendStreamingProviderReasoningDetails(existing, incoming) {
6026
+ const next = [...existing];
6027
+ for (const detail of incoming) {
6028
+ const previous = next[next.length - 1];
6029
+ if (canMergeTextDetails(previous, detail)) {
6030
+ const textField = findMergeableTextField(previous);
6031
+ if (textField && isRecord18(detail)) {
6032
+ const mergedText = mergeStreamingText(String(previous[textField]), String(detail[textField]));
6033
+ if (mergedText === previous[textField]) {
6034
+ continue;
6035
+ }
6036
+ next[next.length - 1] = {
6037
+ ...previous,
6038
+ [textField]: mergedText
6039
+ };
6040
+ continue;
6041
+ }
6042
+ }
6043
+ next.push(detail);
6044
+ }
6045
+ return next;
6046
+ }
6047
+
5966
6048
  // src/runtime-kernel/llm/streaming-adapter.ts
5967
6049
  async function callLlmStream(params) {
5968
6050
  const {
@@ -5975,7 +6057,7 @@ async function callLlmStream(params) {
5975
6057
  } = params;
5976
6058
  let fullResponse = "";
5977
6059
  let streamError = null;
5978
- const reasoningDetails = [];
6060
+ let reasoningDetails = [];
5979
6061
  const streamAnswerId = generateMessageId();
5980
6062
  let streamChunkSeq = 0;
5981
6063
  let capturedUsage = void 0;
@@ -6045,13 +6127,21 @@ async function callLlmStream(params) {
6045
6127
  const reasoning = isRecord17(chunk) ? chunk["reasoning_details"] : void 0;
6046
6128
  if (reasoning !== void 0) {
6047
6129
  const newReasoningDetails = Array.isArray(reasoning) ? reasoning : [reasoning];
6048
- reasoningDetails.push(...newReasoningDetails);
6049
- eventHandler({
6050
- type: "provider_sidecar",
6051
- id: generateMessageId(),
6052
- timestamp: Date.now(),
6053
- reasoning_details: newReasoningDetails
6054
- });
6130
+ const previousReasoningDetails = reasoningDetails;
6131
+ const previousLength = previousReasoningDetails.length;
6132
+ const compactedReasoningDetails = appendStreamingProviderReasoningDetails(reasoningDetails, newReasoningDetails);
6133
+ reasoningDetails = compactedReasoningDetails;
6134
+ const previousLastChanged = previousLength > 0 && compactedReasoningDetails[previousLength - 1] !== previousReasoningDetails[previousLength - 1];
6135
+ const emitFromIndex = previousLastChanged ? previousLength - 1 : previousLength;
6136
+ const emittedReasoningDetails = compactedReasoningDetails.slice(Math.max(0, emitFromIndex));
6137
+ if (emittedReasoningDetails.length > 0) {
6138
+ eventHandler({
6139
+ type: "provider_sidecar",
6140
+ id: generateMessageId(),
6141
+ timestamp: Date.now(),
6142
+ reasoning_details: emittedReasoningDetails
6143
+ });
6144
+ }
6055
6145
  }
6056
6146
  if (chunk.tool_calls) {
6057
6147
  emitThoughtComplete(thoughtSegmenter.onBoundary());
@@ -6622,7 +6712,7 @@ function runRecordToMeta(record) {
6622
6712
  errorIfAny: record.errorIfAny ? { ...record.errorIfAny } : void 0
6623
6713
  };
6624
6714
  }
6625
- function isRecord18(value) {
6715
+ function isRecord19(value) {
6626
6716
  return typeof value === "object" && value !== null && !Array.isArray(value);
6627
6717
  }
6628
6718
  function readStringField(record, key) {
@@ -6643,7 +6733,7 @@ function getRunIdFromMetadata(event) {
6643
6733
  return snakeCaseRunId;
6644
6734
  }
6645
6735
  const runContext = metadata["run_context"];
6646
- if (isRecord18(runContext)) {
6736
+ if (isRecord19(runContext)) {
6647
6737
  return readStringField(runContext, "runId") ?? readStringField(runContext, "run_id");
6648
6738
  }
6649
6739
  return void 0;
@@ -6861,7 +6951,7 @@ function runMetaFromRecord(record) {
6861
6951
  }
6862
6952
 
6863
6953
  // src/runtime-kernel/run-supervisor/runSupervisor.ts
6864
- function isRecord19(value) {
6954
+ function isRecord20(value) {
6865
6955
  return typeof value === "object" && value !== null && !Array.isArray(value);
6866
6956
  }
6867
6957
  function readStringField2(record, key) {
@@ -6878,7 +6968,7 @@ function readRunIdFromRuntimeEvent(event) {
6878
6968
  return directRunId;
6879
6969
  }
6880
6970
  const runContext = metadata["run_context"];
6881
- if (!isRecord19(runContext)) {
6971
+ if (!isRecord20(runContext)) {
6882
6972
  return void 0;
6883
6973
  }
6884
6974
  return readStringField2(runContext, "runId") ?? readStringField2(runContext, "run_id");
@@ -6890,7 +6980,7 @@ function readAwaitingUserReason(event) {
6890
6980
  if (typeof event.prompt === "string" && event.prompt.trim().length > 0) {
6891
6981
  return event.prompt;
6892
6982
  }
6893
- if (isRecord19(event.form)) {
6983
+ if (isRecord20(event.form)) {
6894
6984
  const prompt = readStringField2(event.form, "prompt");
6895
6985
  if (prompt && prompt.trim().length > 0) {
6896
6986
  return prompt;
@@ -7500,7 +7590,7 @@ function createQuickstartTelemetryPort(collector) {
7500
7590
  }
7501
7591
 
7502
7592
  // src/quickstart/runAgent.ts
7503
- function isRecord20(value) {
7593
+ function isRecord21(value) {
7504
7594
  return typeof value === "object" && value !== null && !Array.isArray(value);
7505
7595
  }
7506
7596
  function readString4(value) {
@@ -7514,7 +7604,7 @@ function resolveModelId(agent, options) {
7514
7604
  return modelId;
7515
7605
  }
7516
7606
  function isQuickstartStreamChunkEvent(value) {
7517
- return isRecord20(value) && value.type === "stream_chunk" && typeof value.content === "string";
7607
+ return isRecord21(value) && value.type === "stream_chunk" && typeof value.content === "string";
7518
7608
  }
7519
7609
  function createNoopObservationPreview() {
7520
7610
  return {
@@ -7554,7 +7644,7 @@ function readFinalAnswer(events, checkpointLocal) {
7554
7644
  if (chunks.length > 0) {
7555
7645
  return chunks.join("");
7556
7646
  }
7557
- if (isRecord20(checkpointLocal)) {
7647
+ if (isRecord21(checkpointLocal)) {
7558
7648
  const finalAnswer = checkpointLocal["finalAnswer"];
7559
7649
  if (typeof finalAnswer === "string") {
7560
7650
  return finalAnswer;
@@ -7563,7 +7653,7 @@ function readFinalAnswer(events, checkpointLocal) {
7563
7653
  return "";
7564
7654
  }
7565
7655
  function readContextTrace(checkpointLocal) {
7566
- if (!isRecord20(checkpointLocal)) return void 0;
7656
+ if (!isRecord21(checkpointLocal)) return void 0;
7567
7657
  return checkpointLocal["contextTrace"];
7568
7658
  }
7569
7659
  async function emitRunEvent(event, sink) {