@codehz/ai 0.2.4 → 0.3.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/README.md CHANGED
@@ -164,19 +164,14 @@ const mock = new MockAdapter({
164
164
  });
165
165
  ```
166
166
 
167
- 公开 adapter 接口暴露稳定标识和各维度能力:
167
+ 公开 adapter 接口暴露稳定标识和流来源:
168
168
 
169
169
  ```ts
170
170
  adapter.kind; // "responses" | "messages" | "chat-completions" | ...
171
- adapter.capabilities.textStreaming; // "native" | "synthetic" | "none"
172
- adapter.capabilities.reasoningStreaming;
173
- adapter.capabilities.toolCallStreaming;
174
- adapter.capabilities.replay; // "canonical" | "opaque" | "none"
175
- adapter.capabilities.usage; // "stream" | "final" | "none"
176
- adapter.capabilities.toolResultOutcomes;
171
+ adapter.isSyntheticStream;
177
172
  ```
178
173
 
179
- 响应级 `backend.isSyntheticStream` 根据 `textStreaming === "synthetic"` 推导;具体响应内容仍应从
174
+ 响应级 `backend.isSyntheticStream` 使用同一标记;具体响应内容仍应从
180
175
  本次事件流、warning 和 `replay` 判断。
181
176
 
182
177
  ## Mock 后端
package/dist/index.d.mts CHANGED
@@ -42,7 +42,6 @@ type ToolCallItem = {
42
42
  id: string;
43
43
  name: string;
44
44
  argumentsText: string;
45
- argumentsJson?: unknown;
46
45
  };
47
46
  type ToolResultItem = {
48
47
  type: "tool_result";
@@ -234,18 +233,9 @@ type NormalizedRequest = AIRequest & {
234
233
  model: string;
235
234
  requestId: string;
236
235
  };
237
- type StreamingCapability = "native" | "synthetic" | "none";
238
- type AdapterCapabilities = {
239
- readonly textStreaming: StreamingCapability;
240
- readonly reasoningStreaming: StreamingCapability;
241
- readonly toolCallStreaming: StreamingCapability;
242
- readonly replay: "canonical" | "opaque" | "none";
243
- readonly usage: "stream" | "final" | "none";
244
- readonly toolResultOutcomes: ReadonlyArray<"success" | "error" | "rejected">;
245
- };
246
236
  interface BackendAdapter {
247
237
  readonly kind: "chat-completions" | "messages" | "responses" | "ollama" | "mock";
248
- readonly capabilities: AdapterCapabilities;
238
+ readonly isSyntheticStream: boolean;
249
239
  stream(request: NormalizedRequest): AsyncIterable<AIStreamEvent>;
250
240
  }
251
241
  type CreateAIClientOptions = {
@@ -525,7 +515,7 @@ type StreamResult = {
525
515
  };
526
516
  declare abstract class AdapterBase implements BackendAdapter {
527
517
  abstract readonly kind: "chat-completions" | "messages" | "responses" | "ollama" | "mock";
528
- abstract readonly capabilities: AdapterCapabilities;
518
+ abstract readonly isSyntheticStream: boolean;
529
519
  /**
530
520
  * stream 模板方法:
531
521
  * 1. 创建事件工厂,发射 response.started
@@ -618,7 +608,7 @@ type ResponsesTool = {
618
608
  };
619
609
  declare class ResponsesAdapter extends AdapterBase {
620
610
  readonly kind: "responses";
621
- readonly capabilities: AdapterCapabilities;
611
+ readonly isSyntheticStream = false;
622
612
  private apiKey;
623
613
  private baseUrl;
624
614
  private fetchFn;
@@ -686,7 +676,7 @@ type MessagesAPITool = {
686
676
  };
687
677
  declare class MessagesAdapter extends AdapterBase {
688
678
  readonly kind: "messages";
689
- readonly capabilities: AdapterCapabilities;
679
+ readonly isSyntheticStream = false;
690
680
  private apiKey;
691
681
  private apiVersion;
692
682
  private baseUrl;
@@ -744,7 +734,7 @@ type ChatTool = {
744
734
  };
745
735
  declare class ChatCompletionsAdapter extends AdapterBase {
746
736
  readonly kind: "chat-completions";
747
- readonly capabilities: AdapterCapabilities;
737
+ readonly isSyntheticStream = false;
748
738
  private apiKey;
749
739
  private baseUrl;
750
740
  private fetchFn;
@@ -792,7 +782,7 @@ type OllamaTool = {
792
782
  };
793
783
  declare class OllamaAdapter extends AdapterBase {
794
784
  readonly kind: "ollama";
795
- readonly capabilities: AdapterCapabilities;
785
+ readonly isSyntheticStream = false;
796
786
  private baseUrl;
797
787
  private apiKey;
798
788
  private fetchFn;
@@ -889,7 +879,6 @@ type MockToolCallStep = {
889
879
  id: string;
890
880
  name: string;
891
881
  argumentsText: string;
892
- argumentsJson?: unknown;
893
882
  streamArguments?: boolean;
894
883
  stream?: MockTextStreamOptions | false;
895
884
  };
@@ -942,14 +931,7 @@ type MockProviderRequest = {
942
931
  declare function assertMockRequest(request: NormalizedRequest, expectation: MockRequestExpectation, context: MockHandlerContext): void;
943
932
  declare class MockAdapter extends AdapterBase {
944
933
  readonly kind: "mock";
945
- readonly capabilities: {
946
- readonly textStreaming: "synthetic";
947
- readonly reasoningStreaming: "synthetic";
948
- readonly toolCallStreaming: "synthetic";
949
- readonly replay: "canonical";
950
- readonly usage: "final";
951
- readonly toolResultOutcomes: readonly ["success", "error", "rejected"];
952
- };
934
+ readonly isSyntheticStream = true;
953
935
  private readonly handler;
954
936
  private readonly providerMetadata?;
955
937
  private cursor;
@@ -982,7 +964,7 @@ declare function opaqueBlock(payload: unknown): ContentBlock & {
982
964
  };
983
965
  declare function messageItem(content: ContentBlock[], overrides?: Partial<Omit<MessageItem, "type" | "content">>): MessageItem;
984
966
  declare function reasoningItem(content: ContentBlock[], visibility?: ReasoningItem["visibility"], id?: string): ReasoningItem;
985
- declare function toolCallItem(id: string, name: string, argumentsText: string, argumentsJson?: unknown): ToolCallItem;
967
+ declare function toolCallItem(id: string, name: string, argumentsText: string): ToolCallItem;
986
968
  declare function toolResultItem(callId: string, toolName: string, outcome: ToolResultItem["outcome"], content: ContentBlock[]): ToolResultItem;
987
969
  declare function opaqueItem(source: OpaqueItem["source"], purpose: OpaqueItem["purpose"], payload: unknown, id?: string): OpaqueItem;
988
970
  /**
@@ -1193,27 +1175,20 @@ declare function splitLines(buffer: string, allowEOF: boolean): StreamSplitResul
1193
1175
  declare function splitSSEFrames(buffer: string, allowEOF: boolean): StreamSplitResult;
1194
1176
  //#endregion
1195
1177
  //#region src/helpers/request-mapper.d.ts
1196
- type ProviderProfile = {
1197
- readonly kind: string;
1198
- readonly instructionsMode: "system_message" | "instructions_field" | "none";
1199
- readonly supportedBlockTypes: ReadonlyArray<ContentBlock["type"]>;
1200
- readonly reasoningBlockTypes: ReadonlyArray<ContentBlock["type"]>;
1201
- readonly capabilities: AdapterCapabilities;
1202
- };
1203
1178
  declare class NormalizedRequestMapper {
1204
- readonly profile: ProviderProfile;
1205
- constructor(profile: ProviderProfile);
1179
+ readonly kind: string;
1180
+ constructor(kind: string);
1206
1181
  mapInstructions(instructions: string | InstructionBlock[]): string;
1207
1182
  ensureTextBlocks(blocks: ContentBlock[], field: string): ContentBlock[];
1208
1183
  ensureReasoningBlocks(blocks: ContentBlock[], field: string): Array<Extract<ContentBlock, {
1209
1184
  type: "text";
1210
1185
  }>>;
1211
- assertToolResultOutcome(outcome: ToolResultItem["outcome"]): void;
1186
+ parseToolArguments(item: ToolCallItem): Record<string, unknown>;
1212
1187
  rollbackTrailingAssistantMessages<T extends {
1213
1188
  role: string;
1214
1189
  }>(messages: T[]): void;
1215
1190
  private ensureBlocks;
1216
1191
  }
1217
1192
  //#endregion
1218
- export { type AIClient, AIError, AIMappingError, AIProviderError, type AIRequest, AIRequestError, type AIResponse, AIStreamError, type AIStreamEvent, AdapterAuxiliaryState, AdapterBase, type AdapterCapabilities, AuxiliaryCollector, type AuxiliaryFinalizeOptions, type AuxiliaryFinalizeResult, type AuxiliaryInfo, type BackendAdapter, type BackendTrace, type BillingInfo, type BillingPostprocessHook, type BillingSource, ChatCompletionsAdapter, type ChatCompletionsAdapterOptions, type ContentBlock, type CreateAIClientOptions, type EventFactory, type EventFactoryBackend, type EventFactoryState, type FetchFn, type IncludeSettings, IncrementalStreamParser, type InputItem, type InstructionBlock, type JsonContentBlock, type LookupResult, MAX_OPAQUE_JSON_DEPTH, MAX_OPAQUE_PAYLOAD_BYTES, type MessageCompletedEvent, type MessageDeltaEvent, type MessageItem, type MessageStartedEvent, MessagesAdapter, type MessagesAdapterOptions, MockAdapter, type MockAdapterOptions, type MockAuxiliaryStep, type MockCompleteStep, type MockErrorStep, type MockHandler, type MockHandlerContext, type MockHistoryRecord, type MockInputExpectation, type MockInterruptStep, type MockMessageStep, type MockOutputStep, type MockReasoningStep, type MockRequestExpectation, type MockStaticHandler, type MockStep, type MockTextStreamOptions, type MockThrowStep, type MockToolCallStep, type MockWarningStep, type NormalizeOptions, type NormalizedRequest, NormalizedRequestMapper, OllamaAdapter, type OllamaAdapterOptions, type OpaqueEnvelopeResult, type OpaqueItem, type OutputItem, PROVIDER_ERROR_MESSAGE_MAX_LEN, PROVIDER_ERROR_RAW_BODY_THRESHOLD, type ProviderProfile, type ReasoningCompletedEvent, type ReasoningDeltaEvent, type ReasoningItem, type ReasoningStartedEvent, type ReplayItem, type ResponseAuxiliaryEvent, type ResponseCompletedEvent, type ResponseStartedEvent, type ResponseWarningEvent, ResponsesAdapter, type ResponsesAdapterOptions, type SSEEvent, type StopReason, type StreamEventBase, type StreamParseResult, type StreamResult, type StreamSplitResult, type StreamingCapability, type SyntheticStreamOptions, type TextContentBlock, type ToolCallCompletedEvent, type ToolCallDeltaEvent, type ToolCallItem, type ToolCallStartedEvent, type ToolChoice, type ToolDefinition, type ToolResultItem, type Usage, type UsageSource, type ValidationIssue, WarningCode, aggregateEvents, assertMockRequest, assertOpaqueReplayEnvelope, assertValidRequest, blockToText, collectStream, contentBlocksToText, createAIClient, createEventFactory, emitMalformedStreamWarning, extractProviderErrorMessage, extractText, imageBlock, instructionsToText, jsonBlock, mapReasoningVisibility, mapStopReason, measureJsonDepth, messageItem, metadataSourceList, normalizeRequest, opaqueBlock, opaqueItem, parseSSEEvents, providerHttpError, reasoningItem, replayFromOutput, splitLines, splitSSEFrames, syntheticStream, textBlock, toolCallItem, toolResultItem, usageFromAnthropicMessages, usageFromChatCompletions, usageFromOllama, usageFromOpenAIResponses, validateOpaqueReplayEnvelope, validateRequest, withMockStreaming };
1193
+ export { type AIClient, AIError, AIMappingError, AIProviderError, type AIRequest, AIRequestError, type AIResponse, AIStreamError, type AIStreamEvent, AdapterAuxiliaryState, AdapterBase, AuxiliaryCollector, type AuxiliaryFinalizeOptions, type AuxiliaryFinalizeResult, type AuxiliaryInfo, type BackendAdapter, type BackendTrace, type BillingInfo, type BillingPostprocessHook, type BillingSource, ChatCompletionsAdapter, type ChatCompletionsAdapterOptions, type ContentBlock, type CreateAIClientOptions, type EventFactory, type EventFactoryBackend, type EventFactoryState, type FetchFn, type IncludeSettings, IncrementalStreamParser, type InputItem, type InstructionBlock, type JsonContentBlock, type LookupResult, MAX_OPAQUE_JSON_DEPTH, MAX_OPAQUE_PAYLOAD_BYTES, type MessageCompletedEvent, type MessageDeltaEvent, type MessageItem, type MessageStartedEvent, MessagesAdapter, type MessagesAdapterOptions, MockAdapter, type MockAdapterOptions, type MockAuxiliaryStep, type MockCompleteStep, type MockErrorStep, type MockHandler, type MockHandlerContext, type MockHistoryRecord, type MockInputExpectation, type MockInterruptStep, type MockMessageStep, type MockOutputStep, type MockReasoningStep, type MockRequestExpectation, type MockStaticHandler, type MockStep, type MockTextStreamOptions, type MockThrowStep, type MockToolCallStep, type MockWarningStep, type NormalizeOptions, type NormalizedRequest, NormalizedRequestMapper, OllamaAdapter, type OllamaAdapterOptions, type OpaqueEnvelopeResult, type OpaqueItem, type OutputItem, PROVIDER_ERROR_MESSAGE_MAX_LEN, PROVIDER_ERROR_RAW_BODY_THRESHOLD, type ReasoningCompletedEvent, type ReasoningDeltaEvent, type ReasoningItem, type ReasoningStartedEvent, type ReplayItem, type ResponseAuxiliaryEvent, type ResponseCompletedEvent, type ResponseStartedEvent, type ResponseWarningEvent, ResponsesAdapter, type ResponsesAdapterOptions, type SSEEvent, type StopReason, type StreamEventBase, type StreamParseResult, type StreamResult, type StreamSplitResult, type SyntheticStreamOptions, type TextContentBlock, type ToolCallCompletedEvent, type ToolCallDeltaEvent, type ToolCallItem, type ToolCallStartedEvent, type ToolChoice, type ToolDefinition, type ToolResultItem, type Usage, type UsageSource, type ValidationIssue, WarningCode, aggregateEvents, assertMockRequest, assertOpaqueReplayEnvelope, assertValidRequest, blockToText, collectStream, contentBlocksToText, createAIClient, createEventFactory, emitMalformedStreamWarning, extractProviderErrorMessage, extractText, imageBlock, instructionsToText, jsonBlock, mapReasoningVisibility, mapStopReason, measureJsonDepth, messageItem, metadataSourceList, normalizeRequest, opaqueBlock, opaqueItem, parseSSEEvents, providerHttpError, reasoningItem, replayFromOutput, splitLines, splitSSEFrames, syntheticStream, textBlock, toolCallItem, toolResultItem, usageFromAnthropicMessages, usageFromChatCompletions, usageFromOllama, usageFromOpenAIResponses, validateOpaqueReplayEnvelope, validateRequest, withMockStreaming };
1219
1194
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -156,6 +156,12 @@ function validateInputItem(item, field, issues) {
156
156
  if (typeof item.id !== "string" || item.id.length === 0) pushIssue(issues, `${field}.id`, "TOOL_CALL_ID_INVALID", `${field}.id must be a non-empty string`);
157
157
  if (typeof item.name !== "string" || item.name.length === 0) pushIssue(issues, `${field}.name`, "TOOL_CALL_NAME_INVALID", `${field}.name must be a non-empty string`);
158
158
  if (typeof item.argumentsText !== "string") pushIssue(issues, `${field}.argumentsText`, "TOOL_CALL_ARGUMENTS_INVALID", `${field}.argumentsText must be a string`);
159
+ else try {
160
+ const parsed = JSON.parse(item.argumentsText);
161
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) pushIssue(issues, `${field}.argumentsText`, "TOOL_CALL_ARGUMENTS_INVALID", `${field}.argumentsText must encode a JSON object`);
162
+ } catch {
163
+ pushIssue(issues, `${field}.argumentsText`, "TOOL_CALL_ARGUMENTS_INVALID", `${field}.argumentsText must encode a JSON object`);
164
+ }
159
165
  return;
160
166
  case "tool_result":
161
167
  if (typeof item.callId !== "string" || item.callId.length === 0) pushIssue(issues, `${field}.callId`, "TOOL_RESULT_CALL_ID_INVALID", `${field}.callId must be a non-empty string`);
@@ -820,13 +826,12 @@ function reasoningItem(content, visibility = "full", id) {
820
826
  content
821
827
  };
822
828
  }
823
- function toolCallItem(id, name, argumentsText, argumentsJson) {
829
+ function toolCallItem(id, name, argumentsText) {
824
830
  return {
825
831
  type: "tool_call",
826
832
  id,
827
833
  name,
828
- argumentsText,
829
- argumentsJson
834
+ argumentsText
830
835
  };
831
836
  }
832
837
  function toolResultItem(callId, toolName, outcome, content) {
@@ -1095,7 +1100,7 @@ var AdapterBase = class {
1095
1100
  responseId: request.requestId,
1096
1101
  backend: {
1097
1102
  kind: this.kind,
1098
- isSynthetic: this.capabilities.textStreaming === "synthetic"
1103
+ isSynthetic: this.isSyntheticStream
1099
1104
  }
1100
1105
  });
1101
1106
  yield factory.responseStarted(request.model);
@@ -1147,7 +1152,7 @@ var AdapterBase = class {
1147
1152
  requestId: request.requestId,
1148
1153
  rawResponseId: result.rawResponseId,
1149
1154
  adapter: this.kind,
1150
- isSyntheticStream: this.capabilities.textStreaming === "synthetic",
1155
+ isSyntheticStream: this.isSyntheticStream,
1151
1156
  metadataSources: result.metadataSources,
1152
1157
  warnings
1153
1158
  }
@@ -1566,25 +1571,25 @@ function splitSSEFrames(buffer, allowEOF) {
1566
1571
  //#endregion
1567
1572
  //#region src/helpers/request-mapper.ts
1568
1573
  var NormalizedRequestMapper = class {
1569
- profile;
1570
- constructor(profile) {
1571
- this.profile = profile;
1574
+ kind;
1575
+ constructor(kind) {
1576
+ this.kind = kind;
1572
1577
  }
1573
1578
  mapInstructions(instructions) {
1574
1579
  return typeof instructions === "string" ? instructions : contentBlocksToText(this.ensureTextBlocks(instructions, "instructions"));
1575
1580
  }
1576
1581
  ensureTextBlocks(blocks, field) {
1577
- return this.ensureBlocks(blocks, field, this.profile.supportedBlockTypes, "only text/json blocks are supported");
1582
+ return this.ensureBlocks(blocks, field, ["text", "json"], "only text/json blocks are supported");
1578
1583
  }
1579
1584
  ensureReasoningBlocks(blocks, field) {
1580
- return this.ensureBlocks(blocks, field, this.profile.reasoningBlockTypes, "reasoning only supports text blocks");
1585
+ return this.ensureBlocks(blocks, field, ["text"], "reasoning only supports text blocks");
1581
1586
  }
1582
- assertToolResultOutcome(outcome) {
1583
- if (this.profile.capabilities.toolResultOutcomes.includes(outcome)) return;
1584
- const outcomes = this.profile.capabilities.toolResultOutcomes;
1585
- const supported = outcomes.map((value) => `"${value}"`).join(" and ");
1586
- const verb = outcomes.length > 1 ? "are" : "is";
1587
- throw new AIRequestError(`${this.profile.kind} does not preserve tool_result outcome "${outcome}"; only ${supported} ${verb} supported`, "UNSUPPORTED_TOOL_RESULT_OUTCOME");
1587
+ parseToolArguments(item) {
1588
+ try {
1589
+ const parsed = JSON.parse(item.argumentsText);
1590
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
1591
+ } catch {}
1592
+ throw new AIRequestError(`${this.kind} requires tool_call argumentsText to be a valid JSON object`, "TOOL_CALL_ARGUMENTS_INVALID");
1588
1593
  }
1589
1594
  rollbackTrailingAssistantMessages(messages) {
1590
1595
  while (messages.length > 0 && messages[messages.length - 1]?.role === "assistant") messages.pop();
@@ -1592,7 +1597,7 @@ var NormalizedRequestMapper = class {
1592
1597
  ensureBlocks(blocks, field, supportedTypes, description) {
1593
1598
  for (let i = 0; i < blocks.length; i++) {
1594
1599
  const block = blocks[i];
1595
- if (block && !supportedTypes.includes(block.type)) throw new AIRequestError(`${this.profile.kind} does not support ${field}[${i}] of type "${block.type}"; ${description}`, "UNSUPPORTED_CONTENT_BLOCK");
1600
+ if (block && !supportedTypes.includes(block.type)) throw new AIRequestError(`${this.kind} does not support ${field}[${i}] of type "${block.type}"; ${description}`, "UNSUPPORTED_CONTENT_BLOCK");
1596
1601
  }
1597
1602
  return blocks;
1598
1603
  }
@@ -1609,21 +1614,7 @@ var NormalizedRequestMapper = class {
1609
1614
  *
1610
1615
  * 支持消息流 / reasoning 流 / tool_call 流及高保真 replay。
1611
1616
  */
1612
- const profile$3 = {
1613
- kind: "responses",
1614
- instructionsMode: "instructions_field",
1615
- supportedBlockTypes: ["text", "json"],
1616
- reasoningBlockTypes: ["text"],
1617
- capabilities: {
1618
- textStreaming: "native",
1619
- reasoningStreaming: "native",
1620
- toolCallStreaming: "native",
1621
- replay: "opaque",
1622
- usage: "final",
1623
- toolResultOutcomes: ["success"]
1624
- }
1625
- };
1626
- const mapper$3 = new NormalizedRequestMapper(profile$3);
1617
+ const mapper$3 = new NormalizedRequestMapper("responses");
1627
1618
  /** 已处理或可安全忽略的 Responses SSE 类型(未知类型会 warning 一次)。 */
1628
1619
  const KNOWN_RESPONSES_SSE_TYPES = /* @__PURE__ */ new Set([
1629
1620
  "response.output_item.added",
@@ -1632,8 +1623,6 @@ const KNOWN_RESPONSES_SSE_TYPES = /* @__PURE__ */ new Set([
1632
1623
  "response.output_text.done",
1633
1624
  "response.reasoning.delta",
1634
1625
  "response.reasoning.done",
1635
- "response.tool_call.delta",
1636
- "response.tool_call.done",
1637
1626
  "response.function_call_arguments.delta",
1638
1627
  "response.function_call_arguments.done",
1639
1628
  "response.content_part.added",
@@ -1669,7 +1658,7 @@ function canonicalToResponsesBlock(b) {
1669
1658
  }
1670
1659
  var ResponsesAdapter = class extends AdapterBase {
1671
1660
  kind = "responses";
1672
- capabilities = profile$3.capabilities;
1661
+ isSyntheticStream = false;
1673
1662
  apiKey;
1674
1663
  baseUrl;
1675
1664
  fetchFn;
@@ -1716,7 +1705,6 @@ var ResponsesAdapter = class extends AdapterBase {
1716
1705
  });
1717
1706
  break;
1718
1707
  case "tool_result": {
1719
- mapper$3.assertToolResultOutcome(item.outcome);
1720
1708
  const output = mapper$3.ensureTextBlocks(item.content, `tool_result ${item.callId} content`).map(blockToText).join("\n");
1721
1709
  input.push({
1722
1710
  type: "function_call_output",
@@ -1814,6 +1802,7 @@ var ResponsesAdapter = class extends AdapterBase {
1814
1802
  let completedEmitted = false;
1815
1803
  let unknownEventsWarned = false;
1816
1804
  const messageItemsWithDelta = /* @__PURE__ */ new Set();
1805
+ const toolCallNames = /* @__PURE__ */ new Map();
1817
1806
  try {
1818
1807
  while (true) {
1819
1808
  const { done, value } = await reader.read().catch((err) => {
@@ -1841,9 +1830,12 @@ var ResponsesAdapter = class extends AdapterBase {
1841
1830
  case "reasoning":
1842
1831
  yield factory.reasoningStarted(item.id, "full");
1843
1832
  break;
1844
- case "function_call":
1845
- yield factory.toolCallStarted(item.id, item.name ?? "unknown");
1833
+ case "function_call": {
1834
+ const name = typeof item.name === "string" ? item.name : "unknown";
1835
+ toolCallNames.set(item.id, name);
1836
+ yield factory.toolCallStarted(item.id, name);
1846
1837
  break;
1838
+ }
1847
1839
  }
1848
1840
  continue;
1849
1841
  }
@@ -1871,14 +1863,14 @@ var ResponsesAdapter = class extends AdapterBase {
1871
1863
  output.push(reasoningItem([textBlock(data.text)], "full", data.item_id));
1872
1864
  continue;
1873
1865
  }
1874
- if (sseEvent.type === "response.tool_call.delta") {
1866
+ if (sseEvent.type === "response.function_call_arguments.delta") {
1875
1867
  const data = sseEvent.data;
1876
- if (data.delta.arguments) yield factory.toolCallDelta(data.item_id, { argumentsText: data.delta.arguments });
1868
+ if (data.delta) yield factory.toolCallDelta(data.item_id, { argumentsText: data.delta });
1877
1869
  continue;
1878
1870
  }
1879
- if (sseEvent.type === "response.tool_call.done") {
1871
+ if (sseEvent.type === "response.function_call_arguments.done") {
1880
1872
  const data = sseEvent.data;
1881
- const tcItem = toolCallItem(data.item_id, data.name ?? "unknown", data.arguments ?? "");
1873
+ const tcItem = toolCallItem(data.item_id, toolCallNames.get(data.item_id) ?? "unknown", data.arguments);
1882
1874
  yield factory.toolCallCompleted(data.item_id);
1883
1875
  output.push(tcItem);
1884
1876
  continue;
@@ -1975,21 +1967,7 @@ var ResponsesAdapter = class extends AdapterBase {
1975
1967
  * - 高保真 replay(含 opaque continuation)
1976
1968
  * - 能力降级 warning
1977
1969
  */
1978
- const profile$2 = {
1979
- kind: "messages",
1980
- instructionsMode: "system_message",
1981
- supportedBlockTypes: ["text", "json"],
1982
- reasoningBlockTypes: ["text"],
1983
- capabilities: {
1984
- textStreaming: "native",
1985
- reasoningStreaming: "native",
1986
- toolCallStreaming: "synthetic",
1987
- replay: "opaque",
1988
- usage: "stream",
1989
- toolResultOutcomes: ["success", "error"]
1990
- }
1991
- };
1992
- const mapper$2 = new NormalizedRequestMapper(profile$2);
1970
+ const mapper$2 = new NormalizedRequestMapper("messages");
1993
1971
  function isMessagesReplayContentBlock(value) {
1994
1972
  if (!value || typeof value !== "object" || !("type" in value)) return false;
1995
1973
  const block = value;
@@ -2015,10 +1993,10 @@ function assertMessagesReplayContent(content) {
2015
1993
  function synthesizeItemId(kind, blockIndex, responseId) {
2016
1994
  return `${kind}-${blockIndex}-${responseId}`;
2017
1995
  }
2018
- function parseToolUseInput(input) {
1996
+ function parseProviderToolUseInput(input) {
2019
1997
  try {
2020
1998
  const parsed = JSON.parse(input);
2021
- return parsed && typeof parsed === "object" ? parsed : {};
1999
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
2022
2000
  } catch {
2023
2001
  return {};
2024
2002
  }
@@ -2059,7 +2037,7 @@ function buildStreamMetadata(options) {
2059
2037
  }
2060
2038
  var MessagesAdapter = class extends AdapterBase {
2061
2039
  kind = "messages";
2062
- capabilities = profile$2.capabilities;
2040
+ isSyntheticStream = false;
2063
2041
  apiKey;
2064
2042
  apiVersion;
2065
2043
  baseUrl;
@@ -2098,7 +2076,7 @@ var MessagesAdapter = class extends AdapterBase {
2098
2076
  type: "tool_use",
2099
2077
  id: item.id,
2100
2078
  name: item.name,
2101
- input: item.argumentsJson ?? parseToolUseInput(item.argumentsText)
2079
+ input: mapper$2.parseToolArguments(item)
2102
2080
  };
2103
2081
  if (lastMsg && lastMsg.role === "assistant" && typeof lastMsg.content !== "string") lastMsg.content.push(toolBlock);
2104
2082
  else messages.push({
@@ -2108,13 +2086,12 @@ var MessagesAdapter = class extends AdapterBase {
2108
2086
  break;
2109
2087
  }
2110
2088
  case "tool_result": {
2111
- mapper$2.assertToolResultOutcome(item.outcome);
2112
2089
  const content = mapper$2.ensureTextBlocks(item.content, `tool_result ${item.callId} content`).map(blockToText).join("\n");
2113
2090
  const block = {
2114
2091
  type: "tool_result",
2115
2092
  tool_use_id: item.callId,
2116
2093
  content,
2117
- is_error: item.outcome === "error"
2094
+ is_error: item.outcome !== "success"
2118
2095
  };
2119
2096
  if (pendingToolResultMessage && typeof pendingToolResultMessage.content !== "string") pendingToolResultMessage.content.push(block);
2120
2097
  else {
@@ -2140,7 +2117,7 @@ var MessagesAdapter = class extends AdapterBase {
2140
2117
  break;
2141
2118
  }
2142
2119
  case "opaque": {
2143
- if (item.purpose !== "replay") break;
2120
+ if (item.source !== "messages" || item.purpose !== "replay") break;
2144
2121
  assertOpaqueReplayEnvelope(item.payload);
2145
2122
  const payload = item.payload;
2146
2123
  if (payload.role === "assistant" && "content" in payload) {
@@ -2369,7 +2346,7 @@ var MessagesAdapter = class extends AdapterBase {
2369
2346
  type: "tool_use",
2370
2347
  id: currentItemId,
2371
2348
  name: currentToolName,
2372
- input: parseToolUseInput(currentArgsText || argsBuffer)
2349
+ input: parseProviderToolUseInput(currentArgsText || argsBuffer)
2373
2350
  });
2374
2351
  }
2375
2352
  currentItemType = null;
@@ -2454,21 +2431,7 @@ var MessagesAdapter = class extends AdapterBase {
2454
2431
  * - replay fidelity 依赖 provider 是否暴露可回放的 assistant turn 字段
2455
2432
  */
2456
2433
  const REASONING_FIELDS = ["reasoning_content", "reasoning"];
2457
- const profile$1 = {
2458
- kind: "chat-completions",
2459
- instructionsMode: "system_message",
2460
- supportedBlockTypes: ["text", "json"],
2461
- reasoningBlockTypes: ["text"],
2462
- capabilities: {
2463
- textStreaming: "native",
2464
- reasoningStreaming: "native",
2465
- toolCallStreaming: "native",
2466
- replay: "opaque",
2467
- usage: "final",
2468
- toolResultOutcomes: ["success"]
2469
- }
2470
- };
2471
- const mapper$1 = new NormalizedRequestMapper(profile$1);
2434
+ const mapper$1 = new NormalizedRequestMapper("chat-completions");
2472
2435
  function extractReasoningText(value) {
2473
2436
  if (typeof value === "string") return value;
2474
2437
  if (Array.isArray(value)) return value.map(extractReasoningText).join("");
@@ -2545,7 +2508,7 @@ function buildAssistantReplayMessage(params) {
2545
2508
  }
2546
2509
  var ChatCompletionsAdapter = class extends AdapterBase {
2547
2510
  kind = "chat-completions";
2548
- capabilities = profile$1.capabilities;
2511
+ isSyntheticStream = false;
2549
2512
  apiKey;
2550
2513
  baseUrl;
2551
2514
  fetchFn;
@@ -2590,7 +2553,6 @@ var ChatCompletionsAdapter = class extends AdapterBase {
2590
2553
  break;
2591
2554
  }
2592
2555
  case "tool_result":
2593
- mapper$1.assertToolResultOutcome(item.outcome);
2594
2556
  messages.push({
2595
2557
  role: "tool",
2596
2558
  tool_call_id: item.callId,
@@ -2605,7 +2567,7 @@ var ChatCompletionsAdapter = class extends AdapterBase {
2605
2567
  });
2606
2568
  break;
2607
2569
  case "opaque": {
2608
- if (item.purpose !== "replay") break;
2570
+ if (item.source !== "chat.completions" || item.purpose !== "replay") break;
2609
2571
  assertOpaqueReplayEnvelope(item.payload);
2610
2572
  const payload = item.payload;
2611
2573
  if (payload.role === "assistant" && typeof payload.content === "string") messages.push({
@@ -2920,29 +2882,7 @@ var ChatCompletionsAdapter = class extends AdapterBase {
2920
2882
  * - tool_call 不支持逐 token 流式
2921
2883
  * - replay 保真度低(无 opaque continuation 机制)
2922
2884
  */
2923
- const profile = {
2924
- kind: "ollama",
2925
- instructionsMode: "system_message",
2926
- supportedBlockTypes: ["text", "json"],
2927
- reasoningBlockTypes: ["text"],
2928
- capabilities: {
2929
- textStreaming: "native",
2930
- reasoningStreaming: "none",
2931
- toolCallStreaming: "synthetic",
2932
- replay: "opaque",
2933
- usage: "final",
2934
- toolResultOutcomes: ["success"]
2935
- }
2936
- };
2937
- const mapper = new NormalizedRequestMapper(profile);
2938
- function parseOllamaToolArguments(item) {
2939
- if (item.argumentsJson && typeof item.argumentsJson === "object" && item.argumentsJson !== null) return item.argumentsJson;
2940
- try {
2941
- const parsed = JSON.parse(item.argumentsText);
2942
- if (parsed && typeof parsed === "object") return parsed;
2943
- } catch {}
2944
- throw new AIRequestError("ollama tool_call argumentsText must be valid JSON object when argumentsJson is absent", "TOOL_CALL_ARGUMENTS_INVALID");
2945
- }
2885
+ const mapper = new NormalizedRequestMapper("ollama");
2946
2886
  function isOllamaReplayToolCalls(value) {
2947
2887
  return Array.isArray(value) && value.every((entry) => {
2948
2888
  if (!entry || typeof entry !== "object" || !("function" in entry)) return false;
@@ -2960,7 +2900,7 @@ function toWireOllamaToolCalls(toolCalls) {
2960
2900
  }
2961
2901
  var OllamaAdapter = class extends AdapterBase {
2962
2902
  kind = "ollama";
2963
- capabilities = profile.capabilities;
2903
+ isSyntheticStream = false;
2964
2904
  baseUrl;
2965
2905
  apiKey;
2966
2906
  fetchFn;
@@ -2971,7 +2911,6 @@ var OllamaAdapter = class extends AdapterBase {
2971
2911
  this.fetchFn = options.fetch ?? globalThis.fetch;
2972
2912
  }
2973
2913
  buildRequest(request) {
2974
- if (request.toolChoice && request.toolChoice !== "auto") throw new AIRequestError("ollama does not support explicit toolChoice", "UNSUPPORTED_TOOL_CHOICE");
2975
2914
  const messages = [];
2976
2915
  /** Local-only name → call id queue for best-effort tool_result association (not sent to Ollama). */
2977
2916
  const callIdsByName = /* @__PURE__ */ new Map();
@@ -2992,7 +2931,7 @@ var OllamaAdapter = class extends AdapterBase {
2992
2931
  const lastAssistant = messages.findLast((m) => m.role === "assistant");
2993
2932
  const tc = { function: {
2994
2933
  name: item.name,
2995
- arguments: parseOllamaToolArguments(item)
2934
+ arguments: mapper.parseToolArguments(item)
2996
2935
  } };
2997
2936
  const queue = callIdsByName.get(item.name) ?? [];
2998
2937
  queue.push(item.id);
@@ -3006,7 +2945,6 @@ var OllamaAdapter = class extends AdapterBase {
3006
2945
  break;
3007
2946
  }
3008
2947
  case "tool_result": {
3009
- mapper.assertToolResultOutcome(item.outcome);
3010
2948
  const queue = callIdsByName.get(item.toolName);
3011
2949
  if (queue && queue.length > 0) queue.shift();
3012
2950
  messages.push({
@@ -3053,7 +2991,9 @@ var OllamaAdapter = class extends AdapterBase {
3053
2991
  messages,
3054
2992
  stream: true
3055
2993
  };
3056
- if (request.tools && request.tools.length > 0) body.tools = request.tools.map((t) => ({
2994
+ const toolChoice = request.toolChoice;
2995
+ const selectedTools = toolChoice === "none" ? [] : toolChoice && typeof toolChoice === "object" ? request.tools?.filter((tool) => tool.name === toolChoice.name) : request.tools;
2996
+ if (selectedTools && selectedTools.length > 0) body.tools = selectedTools.map((t) => ({
3057
2997
  type: "function",
3058
2998
  function: {
3059
2999
  name: t.name,
@@ -3071,6 +3011,7 @@ var OllamaAdapter = class extends AdapterBase {
3071
3011
  async *runStream(providerRequest, factory, request) {
3072
3012
  const auxiliary = this.createAuxiliaryState(request);
3073
3013
  let completedEmitted = false;
3014
+ if (request.toolChoice && request.toolChoice !== "auto") yield factory.responseWarning(request.toolChoice === "none" ? "Ollama toolChoice none was mapped by omitting tools" : `Ollama cannot force tool choice; only tool "${request.toolChoice.name}" was provided as a best-effort constraint`, WarningCode.CAPABILITY_DOWNGRADE);
3074
3015
  if (request.metadata) yield factory.responseWarning("Request metadata is not supported by the Ollama adapter", "UNSUPPORTED_METADATA");
3075
3016
  const headers = { "Content-Type": "application/json" };
3076
3017
  if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
@@ -3128,7 +3069,7 @@ var OllamaAdapter = class extends AdapterBase {
3128
3069
  id: tc.id,
3129
3070
  function: {
3130
3071
  name: tc.name,
3131
- arguments: tc.argumentsJson
3072
+ arguments: JSON.parse(tc.argumentsText)
3132
3073
  }
3133
3074
  }))
3134
3075
  }));
@@ -3189,8 +3130,7 @@ var OllamaAdapter = class extends AdapterBase {
3189
3130
  pendingToolCalls.push({
3190
3131
  id: tcId,
3191
3132
  name: tc.function.name,
3192
- argumentsText: argsText,
3193
- argumentsJson: tc.function.arguments
3133
+ argumentsText: argsText
3194
3134
  });
3195
3135
  }
3196
3136
  if (chunk.done) {
@@ -3206,7 +3146,7 @@ var OllamaAdapter = class extends AdapterBase {
3206
3146
  }
3207
3147
  if (pendingToolCalls.length > 0) yield factory.responseWarning(`Ollama delivered ${pendingToolCalls.length} tool call(s) as a batch; tool_call streaming is not supported`, WarningCode.TOOL_CALL_BATCHED);
3208
3148
  for (const pending of pendingToolCalls) {
3209
- const toolCall = toolCallItem(pending.id, pending.name, pending.argumentsText, pending.argumentsJson);
3149
+ const toolCall = toolCallItem(pending.id, pending.name, pending.argumentsText);
3210
3150
  yield factory.toolCallStarted(pending.id, pending.name);
3211
3151
  yield factory.toolCallDelta(pending.id, { argumentsText: pending.argumentsText });
3212
3152
  yield factory.toolCallCompleted(pending.id);
@@ -3248,7 +3188,7 @@ var OllamaAdapter = class extends AdapterBase {
3248
3188
  }
3249
3189
  if (pendingToolCalls.length > 0) yield factory.responseWarning(`Ollama delivered ${pendingToolCalls.length} tool call(s) as a batch; tool_call streaming is not supported`, WarningCode.TOOL_CALL_BATCHED);
3250
3190
  for (const pending of pendingToolCalls) {
3251
- const toolCall = toolCallItem(pending.id, pending.name, pending.argumentsText, pending.argumentsJson);
3191
+ const toolCall = toolCallItem(pending.id, pending.name, pending.argumentsText);
3252
3192
  yield factory.toolCallStarted(pending.id, pending.name);
3253
3193
  yield factory.toolCallDelta(pending.id, { argumentsText: pending.argumentsText });
3254
3194
  yield factory.toolCallCompleted(pending.id);
@@ -3289,18 +3229,7 @@ function assertMockRequest(request, expectation, context) {
3289
3229
  }
3290
3230
  var MockAdapter = class extends AdapterBase {
3291
3231
  kind = "mock";
3292
- capabilities = {
3293
- textStreaming: "synthetic",
3294
- reasoningStreaming: "synthetic",
3295
- toolCallStreaming: "synthetic",
3296
- replay: "canonical",
3297
- usage: "final",
3298
- toolResultOutcomes: [
3299
- "success",
3300
- "error",
3301
- "rejected"
3302
- ]
3303
- };
3232
+ isSyntheticStream = true;
3304
3233
  handler;
3305
3234
  providerMetadata;
3306
3235
  cursor = 0;
@@ -3508,8 +3437,7 @@ function createToolCallFromStep(step) {
3508
3437
  type: "tool_call",
3509
3438
  id: step.id,
3510
3439
  name: step.name,
3511
- argumentsText: step.argumentsText,
3512
- argumentsJson: step.argumentsJson
3440
+ argumentsText: step.argumentsText
3513
3441
  };
3514
3442
  }
3515
3443
  function normalizeBlocks(content) {