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