@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/testkit.cjs CHANGED
@@ -107,11 +107,14 @@ __export(runtime_kernel_exports, {
107
107
  UserNode: () => UserNode,
108
108
  WaitUserNode: () => WaitUserNode,
109
109
  agentHasToolTrigger: () => agentHasToolTrigger,
110
+ appendStreamingProviderReasoningDetails: () => appendStreamingProviderReasoningDetails,
110
111
  applySystemReminders: () => applySystemReminders,
111
112
  audit: () => audit_exports,
112
113
  budgetWarningTrigger: () => budgetWarningTrigger,
113
114
  childRunTrace: () => child_run_trace_exports,
114
115
  childRuns: () => child_runs_exports,
116
+ compactProviderReasoningDetails: () => compactProviderReasoningDetails,
117
+ compactReasoningDetailsInValue: () => compactReasoningDetailsInValue,
115
118
  computeToolIdempotencyKey: () => computeToolIdempotencyKey,
116
119
  consoleAudit: () => consoleAudit,
117
120
  contextBudgetWarningTemplate: () => contextBudgetWarningTemplate,
@@ -196,7 +199,6 @@ var Logger = class {
196
199
  constructor(moduleName) {
197
200
  this.moduleName = moduleName;
198
201
  }
199
- moduleName;
200
202
  debug(message, data) {
201
203
  this.log(0 /* DEBUG */, "debug", message, data);
202
204
  }
@@ -1184,7 +1186,6 @@ var GraphExecutor = class {
1184
1186
  };
1185
1187
  this.telemetryPort = config.telemetryPort ?? noopTelemetry;
1186
1188
  }
1187
- checkpointer;
1188
1189
  nodes = /* @__PURE__ */ new Map();
1189
1190
  ephemeralLocals = /* @__PURE__ */ new Map();
1190
1191
  config;
@@ -4577,6 +4578,22 @@ var ToolNode = class {
4577
4578
  });
4578
4579
  }
4579
4580
  async run(state) {
4581
+ const events = [];
4582
+ while (true) {
4583
+ const result = await this.runNextPendingToolCall(state);
4584
+ if (Array.isArray(result.events) && result.events.length > 0) {
4585
+ events.push(...result.events);
4586
+ }
4587
+ if (result.kind === "route" && result.nextNodeId === "tool") {
4588
+ continue;
4589
+ }
4590
+ return {
4591
+ ...result,
4592
+ events
4593
+ };
4594
+ }
4595
+ }
4596
+ async runNextPendingToolCall(state) {
4580
4597
  const calls = state.local?.pendingToolCalls ?? [];
4581
4598
  const signalRaw = state.local?.signal;
4582
4599
  if (isAbortSignal(signalRaw) && signalRaw.aborted) {
@@ -4784,18 +4801,25 @@ var ToolNode = class {
4784
4801
  rawArguments: context.call.function?.arguments,
4785
4802
  parsedArguments: context.toolArgs
4786
4803
  });
4804
+ const remainingCalls = context.calls.slice(1);
4787
4805
  context.state.local = buildErrorLocalState({
4788
4806
  local: context.local,
4789
- remainingCalls: context.calls.slice(1),
4807
+ remainingCalls,
4790
4808
  conversationId: context.conversationId,
4791
4809
  turnId: context.turnId,
4792
4810
  runtimeEvents: context.bridge.getRuntimeEvents(),
4793
4811
  nextProtocolErrorCount: fuse.nextCount
4794
4812
  });
4795
- if (fuse.shouldFuse) {
4813
+ if (fuse.shouldFuse && remainingCalls.length === 0) {
4796
4814
  throw createToolProtocolFuseError(fuse.nextCount, context.exec.error);
4797
4815
  }
4798
- return { kind: "route", nextNodeId: "llm", events: context.bridge.getRuntimeEvents() };
4816
+ return {
4817
+ kind: "route",
4818
+ // 同一个 assistant.tool_calls batch 必须为每个 call 产出 tool_output。
4819
+ // 出错时也继续消费剩余 call,ToolNode.run 会在本节点内 drain 完 batch 再回 LLM。
4820
+ nextNodeId: remainingCalls.length > 0 ? "tool" : "llm",
4821
+ events: context.bridge.getRuntimeEvents()
4822
+ };
4799
4823
  }
4800
4824
  };
4801
4825
 
@@ -5532,18 +5556,18 @@ function createClassification(category, reason, suggestedDelay, extras) {
5532
5556
  }
5533
5557
  var ErrorClassifier = class {
5534
5558
  static classify(error, context) {
5535
- const isRecord26 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
5559
+ const isRecord27 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
5536
5560
  const baseMsg = (error.message || "").toLowerCase();
5537
5561
  const causeMsg = (() => {
5538
5562
  const cause = error.cause;
5539
5563
  if (typeof cause === "string") return cause.toLowerCase();
5540
5564
  if (cause instanceof Error) return (cause.message || "").toLowerCase();
5541
- if (isRecord26(cause) && typeof cause["message"] === "string") return String(cause["message"]).toLowerCase();
5565
+ if (isRecord27(cause) && typeof cause["message"] === "string") return String(cause["message"]).toLowerCase();
5542
5566
  return "";
5543
5567
  })();
5544
5568
  const causeCode = (() => {
5545
5569
  const cause = error.cause;
5546
- if (isRecord26(cause) && typeof cause["code"] === "string") return String(cause["code"]);
5570
+ if (isRecord27(cause) && typeof cause["code"] === "string") return String(cause["code"]);
5547
5571
  return "";
5548
5572
  })();
5549
5573
  const errorMessage = `${baseMsg} ${causeMsg}`.trim();
@@ -6035,6 +6059,9 @@ __export(llm_exports, {
6035
6059
  LLMPolicyEngine: () => LLMPolicyEngine,
6036
6060
  LlmCaller: () => LlmCaller,
6037
6061
  ModelResolver: () => ModelResolver,
6062
+ appendStreamingProviderReasoningDetails: () => appendStreamingProviderReasoningDetails,
6063
+ compactProviderReasoningDetails: () => compactProviderReasoningDetails,
6064
+ compactReasoningDetailsInValue: () => compactReasoningDetailsInValue,
6038
6065
  createDefaultTokenizerPort: () => createDefaultTokenizerPort,
6039
6066
  defaultPolicyEngine: () => defaultPolicyEngine
6040
6067
  });
@@ -6424,6 +6451,90 @@ function assertToolCallsHaveValidJsonArguments(toolCalls) {
6424
6451
  }
6425
6452
  }
6426
6453
 
6454
+ // src/runtime-kernel/llm/reasoning-details.ts
6455
+ var mergeableTextFields = ["reasoning_content"];
6456
+ function isRecord20(value) {
6457
+ return !!value && typeof value === "object" && !Array.isArray(value);
6458
+ }
6459
+ function findMergeableTextField(detail) {
6460
+ if (!isRecord20(detail)) return void 0;
6461
+ if (typeof detail.provider !== "string") return void 0;
6462
+ if (typeof detail.type !== "string") return void 0;
6463
+ for (const field of mergeableTextFields) {
6464
+ if (typeof detail[field] === "string") {
6465
+ return field;
6466
+ }
6467
+ }
6468
+ return void 0;
6469
+ }
6470
+ function hasOnlyStableTextDetailFields(detail, textField) {
6471
+ const allowedKeys = /* @__PURE__ */ new Set(["provider", "type", textField]);
6472
+ return Object.keys(detail).every((key) => allowedKeys.has(key));
6473
+ }
6474
+ function canMergeTextDetails(previous, incoming) {
6475
+ if (!isRecord20(previous) || !isRecord20(incoming)) return false;
6476
+ const previousField = findMergeableTextField(previous);
6477
+ const incomingField = findMergeableTextField(incoming);
6478
+ if (!previousField || previousField !== incomingField) return false;
6479
+ return previous.provider === incoming.provider && previous.type === incoming.type && hasOnlyStableTextDetailFields(previous, previousField) && hasOnlyStableTextDetailFields(incoming, incomingField);
6480
+ }
6481
+ function mergeStreamingText(previous, incoming) {
6482
+ if (!previous) return incoming;
6483
+ if (!incoming) return previous;
6484
+ if (incoming.startsWith(previous)) {
6485
+ return incoming;
6486
+ }
6487
+ if (previous.endsWith(incoming)) {
6488
+ return previous;
6489
+ }
6490
+ return `${previous}${incoming}`;
6491
+ }
6492
+ function appendStreamingProviderReasoningDetails(existing, incoming) {
6493
+ const next = [...existing];
6494
+ for (const detail of incoming) {
6495
+ const previous = next[next.length - 1];
6496
+ if (canMergeTextDetails(previous, detail)) {
6497
+ const textField = findMergeableTextField(previous);
6498
+ if (textField && isRecord20(detail)) {
6499
+ const mergedText = mergeStreamingText(String(previous[textField]), String(detail[textField]));
6500
+ if (mergedText === previous[textField]) {
6501
+ continue;
6502
+ }
6503
+ next[next.length - 1] = {
6504
+ ...previous,
6505
+ [textField]: mergedText
6506
+ };
6507
+ continue;
6508
+ }
6509
+ }
6510
+ next.push(detail);
6511
+ }
6512
+ return next;
6513
+ }
6514
+ function compactProviderReasoningDetails(reasoningDetails) {
6515
+ return appendStreamingProviderReasoningDetails([], reasoningDetails);
6516
+ }
6517
+ function compactReasoningDetailsInValue(value) {
6518
+ return compactValue(value);
6519
+ }
6520
+ function compactValue(value) {
6521
+ if (Array.isArray(value)) {
6522
+ return value.map((item) => compactValue(item));
6523
+ }
6524
+ if (!isRecord20(value)) {
6525
+ return value;
6526
+ }
6527
+ const compacted = {};
6528
+ for (const [key, childValue] of Object.entries(value)) {
6529
+ if (key === "reasoning_details" && Array.isArray(childValue)) {
6530
+ compacted[key] = compactProviderReasoningDetails(childValue);
6531
+ continue;
6532
+ }
6533
+ compacted[key] = compactValue(childValue);
6534
+ }
6535
+ return compacted;
6536
+ }
6537
+
6427
6538
  // src/runtime-kernel/llm/streaming-adapter.ts
6428
6539
  async function callLlmStream(params) {
6429
6540
  const {
@@ -6436,7 +6547,7 @@ async function callLlmStream(params) {
6436
6547
  } = params;
6437
6548
  let fullResponse = "";
6438
6549
  let streamError = null;
6439
- const reasoningDetails = [];
6550
+ let reasoningDetails = [];
6440
6551
  const streamAnswerId = generateMessageId();
6441
6552
  let streamChunkSeq = 0;
6442
6553
  let capturedUsage = void 0;
@@ -6506,13 +6617,21 @@ async function callLlmStream(params) {
6506
6617
  const reasoning = isRecord19(chunk) ? chunk["reasoning_details"] : void 0;
6507
6618
  if (reasoning !== void 0) {
6508
6619
  const newReasoningDetails = Array.isArray(reasoning) ? reasoning : [reasoning];
6509
- reasoningDetails.push(...newReasoningDetails);
6510
- eventHandler({
6511
- type: "provider_sidecar",
6512
- id: generateMessageId(),
6513
- timestamp: Date.now(),
6514
- reasoning_details: newReasoningDetails
6515
- });
6620
+ const previousReasoningDetails = reasoningDetails;
6621
+ const previousLength = previousReasoningDetails.length;
6622
+ const compactedReasoningDetails = appendStreamingProviderReasoningDetails(reasoningDetails, newReasoningDetails);
6623
+ reasoningDetails = compactedReasoningDetails;
6624
+ const previousLastChanged = previousLength > 0 && compactedReasoningDetails[previousLength - 1] !== previousReasoningDetails[previousLength - 1];
6625
+ const emitFromIndex = previousLastChanged ? previousLength - 1 : previousLength;
6626
+ const emittedReasoningDetails = compactedReasoningDetails.slice(Math.max(0, emitFromIndex));
6627
+ if (emittedReasoningDetails.length > 0) {
6628
+ eventHandler({
6629
+ type: "provider_sidecar",
6630
+ id: generateMessageId(),
6631
+ timestamp: Date.now(),
6632
+ reasoning_details: emittedReasoningDetails
6633
+ });
6634
+ }
6516
6635
  }
6517
6636
  if (chunk.tool_calls) {
6518
6637
  emitThoughtComplete(thoughtSegmenter.onBoundary());
@@ -6938,7 +7057,6 @@ var DefaultTokenizerPort = class {
6938
7057
  constructor(config = {}) {
6939
7058
  this.config = config;
6940
7059
  }
6941
- config;
6942
7060
  estimateText(text, _modelId) {
6943
7061
  return TokenCalculator.estimateTokens(text, {
6944
7062
  encoding: this.config.encoding,
@@ -7080,8 +7198,6 @@ var FinalAnswerCollector = class {
7080
7198
  this.conversationId = conversationId;
7081
7199
  this.turnId = turnId;
7082
7200
  }
7083
- conversationId;
7084
- turnId;
7085
7201
  answerId;
7086
7202
  chunks = [];
7087
7203
  chunkCount = 0;
@@ -7129,7 +7245,7 @@ var FinalAnswerCollector = class {
7129
7245
  };
7130
7246
 
7131
7247
  // src/runtime-kernel/child-runs/childRunTraceSink.ts
7132
- function isRecord20(v) {
7248
+ function isRecord21(v) {
7133
7249
  return !!v && typeof v === "object" && !Array.isArray(v);
7134
7250
  }
7135
7251
  function getString2(obj, key) {
@@ -7162,7 +7278,7 @@ function createChildRunTraceSink(params) {
7162
7278
  const { publisher, conversationId, turnId } = params;
7163
7279
  const finalAnswerCollector = new FinalAnswerCollector(conversationId, turnId);
7164
7280
  const sink = (evt) => {
7165
- if (!isRecord20(evt)) {
7281
+ if (!isRecord21(evt)) {
7166
7282
  return [];
7167
7283
  }
7168
7284
  const type = getString2(evt, "type");
@@ -7831,7 +7947,7 @@ function runRecordToMeta(record) {
7831
7947
  errorIfAny: record.errorIfAny ? { ...record.errorIfAny } : void 0
7832
7948
  };
7833
7949
  }
7834
- function isRecord21(value) {
7950
+ function isRecord22(value) {
7835
7951
  return typeof value === "object" && value !== null && !Array.isArray(value);
7836
7952
  }
7837
7953
  function readStringField(record, key) {
@@ -7852,7 +7968,7 @@ function getRunIdFromMetadata(event) {
7852
7968
  return snakeCaseRunId;
7853
7969
  }
7854
7970
  const runContext = metadata["run_context"];
7855
- if (isRecord21(runContext)) {
7971
+ if (isRecord22(runContext)) {
7856
7972
  return readStringField(runContext, "runId") ?? readStringField(runContext, "run_id");
7857
7973
  }
7858
7974
  return void 0;
@@ -8070,7 +8186,7 @@ function runMetaFromRecord(record) {
8070
8186
  }
8071
8187
 
8072
8188
  // src/runtime-kernel/run-supervisor/runSupervisor.ts
8073
- function isRecord22(value) {
8189
+ function isRecord23(value) {
8074
8190
  return typeof value === "object" && value !== null && !Array.isArray(value);
8075
8191
  }
8076
8192
  function readStringField2(record, key) {
@@ -8087,7 +8203,7 @@ function readRunIdFromRuntimeEvent(event) {
8087
8203
  return directRunId;
8088
8204
  }
8089
8205
  const runContext = metadata["run_context"];
8090
- if (!isRecord22(runContext)) {
8206
+ if (!isRecord23(runContext)) {
8091
8207
  return void 0;
8092
8208
  }
8093
8209
  return readStringField2(runContext, "runId") ?? readStringField2(runContext, "run_id");
@@ -8099,7 +8215,7 @@ function readAwaitingUserReason(event) {
8099
8215
  if (typeof event.prompt === "string" && event.prompt.trim().length > 0) {
8100
8216
  return event.prompt;
8101
8217
  }
8102
- if (isRecord22(event.form)) {
8218
+ if (isRecord23(event.form)) {
8103
8219
  const prompt = readStringField2(event.form, "prompt");
8104
8220
  if (prompt && prompt.trim().length > 0) {
8105
8221
  return prompt;
@@ -8985,11 +9101,11 @@ new MessageFormatter();
8985
9101
 
8986
9102
  // src/context-manager/profiles/agent/utils/toolInteractionGroup.ts
8987
9103
  var CHECKPOINT_TOOL_NAME = "context_checkpoint";
8988
- function isRecord23(value) {
9104
+ function isRecord24(value) {
8989
9105
  return typeof value === "object" && value !== null && !Array.isArray(value);
8990
9106
  }
8991
9107
  function isToolCallRecord(value) {
8992
- if (!isRecord23(value)) {
9108
+ if (!isRecord24(value)) {
8993
9109
  return false;
8994
9110
  }
8995
9111
  return typeof value.id === "string" && value.id.trim().length > 0;
@@ -9007,7 +9123,7 @@ function safeParseArgs(rawArgs) {
9007
9123
  }
9008
9124
  try {
9009
9125
  const parsed = JSON.parse(rawArgs);
9010
- return isRecord23(parsed) ? parsed : {};
9126
+ return isRecord24(parsed) ? parsed : {};
9011
9127
  } catch {
9012
9128
  return {};
9013
9129
  }
@@ -9301,7 +9417,7 @@ var summarizeToolOutput = (toolName, output, toolSummaryProvider, toolArgs, conf
9301
9417
  };
9302
9418
 
9303
9419
  // src/context-manager/profiles/agent/utils/eventConverter.ts
9304
- function isRecord24(value) {
9420
+ function isRecord25(value) {
9305
9421
  return !!value && typeof value === "object" && !Array.isArray(value);
9306
9422
  }
9307
9423
  function isHistorySummaryEvent2(event) {
@@ -9359,10 +9475,10 @@ function convertEventToAiMessage(event) {
9359
9475
  timestamp: event.timestamp,
9360
9476
  metadata: {
9361
9477
  tool_calls: (() => {
9362
- const isRecord26 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
9478
+ const isRecord27 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
9363
9479
  const payload = event.payload;
9364
9480
  const toolCallsFromPayload = (() => {
9365
- if (!isRecord26(payload)) return void 0;
9481
+ if (!isRecord27(payload)) return void 0;
9366
9482
  const raw = payload["tool_calls"];
9367
9483
  return Array.isArray(raw) ? raw : void 0;
9368
9484
  })();
@@ -9381,9 +9497,9 @@ function convertEventToAiMessage(event) {
9381
9497
  ];
9382
9498
  })(),
9383
9499
  reasoning_details: (() => {
9384
- const isRecord26 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
9500
+ const isRecord27 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
9385
9501
  const payload = event.payload;
9386
- if (!isRecord26(payload)) return void 0;
9502
+ if (!isRecord27(payload)) return void 0;
9387
9503
  const rd = payload["reasoning_details"];
9388
9504
  return Array.isArray(rd) ? rd : void 0;
9389
9505
  })()
@@ -9392,15 +9508,15 @@ function convertEventToAiMessage(event) {
9392
9508
  case "tool_output": {
9393
9509
  const observationFromPayload = (() => {
9394
9510
  const payload = event.payload;
9395
- if (!isRecord24(payload)) return void 0;
9511
+ if (!isRecord25(payload)) return void 0;
9396
9512
  const result = payload["result"];
9397
- if (!isRecord24(result)) return void 0;
9513
+ if (!isRecord25(result)) return void 0;
9398
9514
  const obs = result["observation"];
9399
9515
  return typeof obs === "string" && obs.trim().length > 0 ? obs : void 0;
9400
9516
  })();
9401
9517
  const rawOutput = (() => {
9402
9518
  const payload = event.payload;
9403
- if (isRecord24(payload)) {
9519
+ if (isRecord25(payload)) {
9404
9520
  const out = payload["output"];
9405
9521
  if (typeof out === "string" && out.trim().length > 0) {
9406
9522
  return out;
@@ -9525,7 +9641,7 @@ __export(utils_exports, {
9525
9641
  });
9526
9642
 
9527
9643
  // src/testkit/context-harness/replayHarness.ts
9528
- function isRecord25(value) {
9644
+ function isRecord26(value) {
9529
9645
  return !!value && typeof value === "object" && !Array.isArray(value);
9530
9646
  }
9531
9647
  function createReplayHarness(events) {
@@ -9537,7 +9653,7 @@ function createReplayHarness(events) {
9537
9653
  if (message.role !== "assistant" || message.type !== "tool_calls") {
9538
9654
  return false;
9539
9655
  }
9540
- if (!isRecord25(message.metadata)) {
9656
+ if (!isRecord26(message.metadata)) {
9541
9657
  return false;
9542
9658
  }
9543
9659
  return Array.isArray(message.metadata.tool_calls);