@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.
package/dist/cli.js CHANGED
@@ -1081,7 +1081,6 @@ var Logger = class {
1081
1081
  constructor(moduleName) {
1082
1082
  this.moduleName = moduleName;
1083
1083
  }
1084
- moduleName;
1085
1084
  debug(message, data) {
1086
1085
  this.log(0 /* DEBUG */, "debug", message, data);
1087
1086
  }
@@ -1163,7 +1162,6 @@ var GraphExecutor = class {
1163
1162
  };
1164
1163
  this.telemetryPort = config.telemetryPort ?? noopTelemetry;
1165
1164
  }
1166
- checkpointer;
1167
1165
  nodes = /* @__PURE__ */ new Map();
1168
1166
  ephemeralLocals = /* @__PURE__ */ new Map();
1169
1167
  config;
@@ -4445,6 +4443,22 @@ var ToolNode = class {
4445
4443
  });
4446
4444
  }
4447
4445
  async run(state) {
4446
+ const events = [];
4447
+ while (true) {
4448
+ const result = await this.runNextPendingToolCall(state);
4449
+ if (Array.isArray(result.events) && result.events.length > 0) {
4450
+ events.push(...result.events);
4451
+ }
4452
+ if (result.kind === "route" && result.nextNodeId === "tool") {
4453
+ continue;
4454
+ }
4455
+ return {
4456
+ ...result,
4457
+ events
4458
+ };
4459
+ }
4460
+ }
4461
+ async runNextPendingToolCall(state) {
4448
4462
  const calls = state.local?.pendingToolCalls ?? [];
4449
4463
  const signalRaw = state.local?.signal;
4450
4464
  if (isAbortSignal(signalRaw) && signalRaw.aborted) {
@@ -4652,18 +4666,25 @@ var ToolNode = class {
4652
4666
  rawArguments: context.call.function?.arguments,
4653
4667
  parsedArguments: context.toolArgs
4654
4668
  });
4669
+ const remainingCalls = context.calls.slice(1);
4655
4670
  context.state.local = buildErrorLocalState({
4656
4671
  local: context.local,
4657
- remainingCalls: context.calls.slice(1),
4672
+ remainingCalls,
4658
4673
  conversationId: context.conversationId,
4659
4674
  turnId: context.turnId,
4660
4675
  runtimeEvents: context.bridge.getRuntimeEvents(),
4661
4676
  nextProtocolErrorCount: fuse.nextCount
4662
4677
  });
4663
- if (fuse.shouldFuse) {
4678
+ if (fuse.shouldFuse && remainingCalls.length === 0) {
4664
4679
  throw createToolProtocolFuseError(fuse.nextCount, context.exec.error);
4665
4680
  }
4666
- return { kind: "route", nextNodeId: "llm", events: context.bridge.getRuntimeEvents() };
4681
+ return {
4682
+ kind: "route",
4683
+ // 同一个 assistant.tool_calls batch 必须为每个 call 产出 tool_output。
4684
+ // 出错时也继续消费剩余 call,ToolNode.run 会在本节点内 drain 完 batch 再回 LLM。
4685
+ nextNodeId: remainingCalls.length > 0 ? "tool" : "llm",
4686
+ events: context.bridge.getRuntimeEvents()
4687
+ };
4667
4688
  }
4668
4689
  };
4669
4690
 
@@ -5159,18 +5180,18 @@ function createClassification(category, reason, suggestedDelay, extras) {
5159
5180
  }
5160
5181
  var ErrorClassifier = class {
5161
5182
  static classify(error, context) {
5162
- const isRecord22 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
5183
+ const isRecord23 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
5163
5184
  const baseMsg = (error.message || "").toLowerCase();
5164
5185
  const causeMsg = (() => {
5165
5186
  const cause = error.cause;
5166
5187
  if (typeof cause === "string") return cause.toLowerCase();
5167
5188
  if (cause instanceof Error) return (cause.message || "").toLowerCase();
5168
- if (isRecord22(cause) && typeof cause["message"] === "string") return String(cause["message"]).toLowerCase();
5189
+ if (isRecord23(cause) && typeof cause["message"] === "string") return String(cause["message"]).toLowerCase();
5169
5190
  return "";
5170
5191
  })();
5171
5192
  const causeCode = (() => {
5172
5193
  const cause = error.cause;
5173
- if (isRecord22(cause) && typeof cause["code"] === "string") return String(cause["code"]);
5194
+ if (isRecord23(cause) && typeof cause["code"] === "string") return String(cause["code"]);
5174
5195
  return "";
5175
5196
  })();
5176
5197
  const errorMessage = `${baseMsg} ${causeMsg}`.trim();
@@ -5846,6 +5867,67 @@ function assertToolCallsHaveValidJsonArguments(toolCalls) {
5846
5867
  }
5847
5868
  }
5848
5869
 
5870
+ // src/runtime-kernel/llm/reasoning-details.ts
5871
+ var mergeableTextFields = ["reasoning_content"];
5872
+ function isRecord18(value) {
5873
+ return !!value && typeof value === "object" && !Array.isArray(value);
5874
+ }
5875
+ function findMergeableTextField(detail) {
5876
+ if (!isRecord18(detail)) return void 0;
5877
+ if (typeof detail.provider !== "string") return void 0;
5878
+ if (typeof detail.type !== "string") return void 0;
5879
+ for (const field of mergeableTextFields) {
5880
+ if (typeof detail[field] === "string") {
5881
+ return field;
5882
+ }
5883
+ }
5884
+ return void 0;
5885
+ }
5886
+ function hasOnlyStableTextDetailFields(detail, textField) {
5887
+ const allowedKeys = /* @__PURE__ */ new Set(["provider", "type", textField]);
5888
+ return Object.keys(detail).every((key) => allowedKeys.has(key));
5889
+ }
5890
+ function canMergeTextDetails(previous, incoming) {
5891
+ if (!isRecord18(previous) || !isRecord18(incoming)) return false;
5892
+ const previousField = findMergeableTextField(previous);
5893
+ const incomingField = findMergeableTextField(incoming);
5894
+ if (!previousField || previousField !== incomingField) return false;
5895
+ return previous.provider === incoming.provider && previous.type === incoming.type && hasOnlyStableTextDetailFields(previous, previousField) && hasOnlyStableTextDetailFields(incoming, incomingField);
5896
+ }
5897
+ function mergeStreamingText(previous, incoming) {
5898
+ if (!previous) return incoming;
5899
+ if (!incoming) return previous;
5900
+ if (incoming.startsWith(previous)) {
5901
+ return incoming;
5902
+ }
5903
+ if (previous.endsWith(incoming)) {
5904
+ return previous;
5905
+ }
5906
+ return `${previous}${incoming}`;
5907
+ }
5908
+ function appendStreamingProviderReasoningDetails(existing, incoming) {
5909
+ const next = [...existing];
5910
+ for (const detail of incoming) {
5911
+ const previous = next[next.length - 1];
5912
+ if (canMergeTextDetails(previous, detail)) {
5913
+ const textField = findMergeableTextField(previous);
5914
+ if (textField && isRecord18(detail)) {
5915
+ const mergedText = mergeStreamingText(String(previous[textField]), String(detail[textField]));
5916
+ if (mergedText === previous[textField]) {
5917
+ continue;
5918
+ }
5919
+ next[next.length - 1] = {
5920
+ ...previous,
5921
+ [textField]: mergedText
5922
+ };
5923
+ continue;
5924
+ }
5925
+ }
5926
+ next.push(detail);
5927
+ }
5928
+ return next;
5929
+ }
5930
+
5849
5931
  // src/runtime-kernel/llm/streaming-adapter.ts
5850
5932
  async function callLlmStream(params) {
5851
5933
  const {
@@ -5858,7 +5940,7 @@ async function callLlmStream(params) {
5858
5940
  } = params;
5859
5941
  let fullResponse = "";
5860
5942
  let streamError = null;
5861
- const reasoningDetails = [];
5943
+ let reasoningDetails = [];
5862
5944
  const streamAnswerId = generateMessageId();
5863
5945
  let streamChunkSeq = 0;
5864
5946
  let capturedUsage = void 0;
@@ -5928,13 +6010,21 @@ async function callLlmStream(params) {
5928
6010
  const reasoning = isRecord17(chunk) ? chunk["reasoning_details"] : void 0;
5929
6011
  if (reasoning !== void 0) {
5930
6012
  const newReasoningDetails = Array.isArray(reasoning) ? reasoning : [reasoning];
5931
- reasoningDetails.push(...newReasoningDetails);
5932
- eventHandler({
5933
- type: "provider_sidecar",
5934
- id: generateMessageId(),
5935
- timestamp: Date.now(),
5936
- reasoning_details: newReasoningDetails
5937
- });
6013
+ const previousReasoningDetails = reasoningDetails;
6014
+ const previousLength = previousReasoningDetails.length;
6015
+ const compactedReasoningDetails = appendStreamingProviderReasoningDetails(reasoningDetails, newReasoningDetails);
6016
+ reasoningDetails = compactedReasoningDetails;
6017
+ const previousLastChanged = previousLength > 0 && compactedReasoningDetails[previousLength - 1] !== previousReasoningDetails[previousLength - 1];
6018
+ const emitFromIndex = previousLastChanged ? previousLength - 1 : previousLength;
6019
+ const emittedReasoningDetails = compactedReasoningDetails.slice(Math.max(0, emitFromIndex));
6020
+ if (emittedReasoningDetails.length > 0) {
6021
+ eventHandler({
6022
+ type: "provider_sidecar",
6023
+ id: generateMessageId(),
6024
+ timestamp: Date.now(),
6025
+ reasoning_details: emittedReasoningDetails
6026
+ });
6027
+ }
5938
6028
  }
5939
6029
  if (chunk.tool_calls) {
5940
6030
  emitThoughtComplete(thoughtSegmenter.onBoundary());
@@ -6505,7 +6595,7 @@ function runRecordToMeta(record) {
6505
6595
  errorIfAny: record.errorIfAny ? { ...record.errorIfAny } : void 0
6506
6596
  };
6507
6597
  }
6508
- function isRecord18(value) {
6598
+ function isRecord19(value) {
6509
6599
  return typeof value === "object" && value !== null && !Array.isArray(value);
6510
6600
  }
6511
6601
  function readStringField(record, key) {
@@ -6526,7 +6616,7 @@ function getRunIdFromMetadata(event) {
6526
6616
  return snakeCaseRunId;
6527
6617
  }
6528
6618
  const runContext = metadata["run_context"];
6529
- if (isRecord18(runContext)) {
6619
+ if (isRecord19(runContext)) {
6530
6620
  return readStringField(runContext, "runId") ?? readStringField(runContext, "run_id");
6531
6621
  }
6532
6622
  return void 0;
@@ -6744,7 +6834,7 @@ function runMetaFromRecord(record) {
6744
6834
  }
6745
6835
 
6746
6836
  // src/runtime-kernel/run-supervisor/runSupervisor.ts
6747
- function isRecord19(value) {
6837
+ function isRecord20(value) {
6748
6838
  return typeof value === "object" && value !== null && !Array.isArray(value);
6749
6839
  }
6750
6840
  function readStringField2(record, key) {
@@ -6761,7 +6851,7 @@ function readRunIdFromRuntimeEvent(event) {
6761
6851
  return directRunId;
6762
6852
  }
6763
6853
  const runContext = metadata["run_context"];
6764
- if (!isRecord19(runContext)) {
6854
+ if (!isRecord20(runContext)) {
6765
6855
  return void 0;
6766
6856
  }
6767
6857
  return readStringField2(runContext, "runId") ?? readStringField2(runContext, "run_id");
@@ -6773,7 +6863,7 @@ function readAwaitingUserReason(event) {
6773
6863
  if (typeof event.prompt === "string" && event.prompt.trim().length > 0) {
6774
6864
  return event.prompt;
6775
6865
  }
6776
- if (isRecord19(event.form)) {
6866
+ if (isRecord20(event.form)) {
6777
6867
  const prompt = readStringField2(event.form, "prompt");
6778
6868
  if (prompt && prompt.trim().length > 0) {
6779
6869
  return prompt;
@@ -7383,7 +7473,7 @@ function createQuickstartTelemetryPort(collector) {
7383
7473
  }
7384
7474
 
7385
7475
  // src/quickstart/runAgent.ts
7386
- function isRecord20(value) {
7476
+ function isRecord21(value) {
7387
7477
  return typeof value === "object" && value !== null && !Array.isArray(value);
7388
7478
  }
7389
7479
  function readString4(value) {
@@ -7397,7 +7487,7 @@ function resolveModelId(agent, options) {
7397
7487
  return modelId;
7398
7488
  }
7399
7489
  function isQuickstartStreamChunkEvent(value) {
7400
- return isRecord20(value) && value.type === "stream_chunk" && typeof value.content === "string";
7490
+ return isRecord21(value) && value.type === "stream_chunk" && typeof value.content === "string";
7401
7491
  }
7402
7492
  function createNoopObservationPreview() {
7403
7493
  return {
@@ -7437,7 +7527,7 @@ function readFinalAnswer(events, checkpointLocal) {
7437
7527
  if (chunks.length > 0) {
7438
7528
  return chunks.join("");
7439
7529
  }
7440
- if (isRecord20(checkpointLocal)) {
7530
+ if (isRecord21(checkpointLocal)) {
7441
7531
  const finalAnswer = checkpointLocal["finalAnswer"];
7442
7532
  if (typeof finalAnswer === "string") {
7443
7533
  return finalAnswer;
@@ -7446,7 +7536,7 @@ function readFinalAnswer(events, checkpointLocal) {
7446
7536
  return "";
7447
7537
  }
7448
7538
  function readContextTrace(checkpointLocal) {
7449
- if (!isRecord20(checkpointLocal)) return void 0;
7539
+ if (!isRecord21(checkpointLocal)) return void 0;
7450
7540
  return checkpointLocal["contextTrace"];
7451
7541
  }
7452
7542
  async function emitRunEvent(event, sink) {
@@ -7570,11 +7660,11 @@ async function runAgent(agent, options) {
7570
7660
  }
7571
7661
 
7572
7662
  // src/cli/configLoader.ts
7573
- function isRecord21(value) {
7663
+ function isRecord22(value) {
7574
7664
  return typeof value === "object" && value !== null && !Array.isArray(value);
7575
7665
  }
7576
7666
  function readDefaultExport(moduleValue) {
7577
- if (isRecord21(moduleValue) && "default" in moduleValue) {
7667
+ if (isRecord22(moduleValue) && "default" in moduleValue) {
7578
7668
  return moduleValue.default;
7579
7669
  }
7580
7670
  return moduleValue;
@@ -7585,7 +7675,7 @@ async function loadConfig(configPath, cwd) {
7585
7675
  moduleUrl.searchParams.set("t", String(Date.now()));
7586
7676
  const loaded = await import(moduleUrl.href);
7587
7677
  const config = readDefaultExport(loaded);
7588
- if (!isRecord21(config)) {
7678
+ if (!isRecord22(config)) {
7589
7679
  throw new Error(`[linnkit] config must export an object: ${absolutePath}`);
7590
7680
  }
7591
7681
  return defineConfig(config);