@strands-agents/sdk 1.0.0-rc.5 → 1.0.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.
Files changed (250) hide show
  1. package/LICENSE +175 -0
  2. package/README.md +340 -0
  3. package/dist/src/__fixtures__/agent-helpers.d.ts +6 -0
  4. package/dist/src/__fixtures__/agent-helpers.d.ts.map +1 -1
  5. package/dist/src/__fixtures__/agent-helpers.js +3 -1
  6. package/dist/src/__fixtures__/agent-helpers.js.map +1 -1
  7. package/dist/src/__fixtures__/mock-plugin.d.ts.map +1 -1
  8. package/dist/src/__fixtures__/mock-plugin.js +3 -1
  9. package/dist/src/__fixtures__/mock-plugin.js.map +1 -1
  10. package/dist/src/__fixtures__/tool-helpers.d.ts +3 -1
  11. package/dist/src/__fixtures__/tool-helpers.d.ts.map +1 -1
  12. package/dist/src/__fixtures__/tool-helpers.js +3 -1
  13. package/dist/src/__fixtures__/tool-helpers.js.map +1 -1
  14. package/dist/src/__tests__/mcp.test.js +222 -2
  15. package/dist/src/__tests__/mcp.test.js.map +1 -1
  16. package/dist/src/a2a/__tests__/events.test.js +2 -0
  17. package/dist/src/a2a/__tests__/events.test.js.map +1 -1
  18. package/dist/src/a2a/__tests__/executor.test.js +16 -5
  19. package/dist/src/a2a/__tests__/executor.test.js.map +1 -1
  20. package/dist/src/a2a/a2a-agent.d.ts +8 -3
  21. package/dist/src/a2a/a2a-agent.d.ts.map +1 -1
  22. package/dist/src/a2a/a2a-agent.js +12 -6
  23. package/dist/src/a2a/a2a-agent.js.map +1 -1
  24. package/dist/src/a2a/executor.d.ts +13 -0
  25. package/dist/src/a2a/executor.d.ts.map +1 -1
  26. package/dist/src/a2a/executor.js +19 -1
  27. package/dist/src/a2a/executor.js.map +1 -1
  28. package/dist/src/agent/__tests__/agent-as-tool.invocation-state.test.d.ts +2 -0
  29. package/dist/src/agent/__tests__/agent-as-tool.invocation-state.test.d.ts.map +1 -0
  30. package/dist/src/agent/__tests__/agent-as-tool.invocation-state.test.js +23 -0
  31. package/dist/src/agent/__tests__/agent-as-tool.invocation-state.test.js.map +1 -0
  32. package/dist/src/agent/__tests__/agent.cancel.test.js +1 -1
  33. package/dist/src/agent/__tests__/agent.cancel.test.js.map +1 -1
  34. package/dist/src/agent/__tests__/agent.concurrent.test.d.ts +2 -0
  35. package/dist/src/agent/__tests__/agent.concurrent.test.d.ts.map +1 -0
  36. package/dist/src/agent/__tests__/agent.concurrent.test.js +488 -0
  37. package/dist/src/agent/__tests__/agent.concurrent.test.js.map +1 -0
  38. package/dist/src/agent/__tests__/agent.hook.test.js +174 -12
  39. package/dist/src/agent/__tests__/agent.hook.test.js.map +1 -1
  40. package/dist/src/agent/__tests__/agent.invocation-state.test.d.ts +2 -0
  41. package/dist/src/agent/__tests__/agent.invocation-state.test.d.ts.map +1 -0
  42. package/dist/src/agent/__tests__/agent.invocation-state.test.js +219 -0
  43. package/dist/src/agent/__tests__/agent.invocation-state.test.js.map +1 -0
  44. package/dist/src/agent/__tests__/agent.stateful-model.test.d.ts +2 -0
  45. package/dist/src/agent/__tests__/agent.stateful-model.test.d.ts.map +1 -0
  46. package/dist/src/agent/__tests__/agent.stateful-model.test.js +169 -0
  47. package/dist/src/agent/__tests__/agent.stateful-model.test.js.map +1 -0
  48. package/dist/src/agent/__tests__/agent.test.js +99 -2
  49. package/dist/src/agent/__tests__/agent.test.js.map +1 -1
  50. package/dist/src/agent/__tests__/agent.tracer.test.node.js +39 -0
  51. package/dist/src/agent/__tests__/agent.tracer.test.node.js.map +1 -1
  52. package/dist/src/agent/__tests__/snapshot.test.js +5 -4
  53. package/dist/src/agent/__tests__/snapshot.test.js.map +1 -1
  54. package/dist/src/agent/agent-as-tool.d.ts.map +1 -1
  55. package/dist/src/agent/agent-as-tool.js +4 -2
  56. package/dist/src/agent/agent-as-tool.js.map +1 -1
  57. package/dist/src/agent/agent.d.ts +75 -1
  58. package/dist/src/agent/agent.d.ts.map +1 -1
  59. package/dist/src/agent/agent.js +323 -83
  60. package/dist/src/agent/agent.js.map +1 -1
  61. package/dist/src/agent/snapshot.d.ts +2 -2
  62. package/dist/src/agent/snapshot.d.ts.map +1 -1
  63. package/dist/src/agent/snapshot.js +8 -2
  64. package/dist/src/agent/snapshot.js.map +1 -1
  65. package/dist/src/conversation-manager/__tests__/conversation-manager.test.js +4 -4
  66. package/dist/src/conversation-manager/__tests__/conversation-manager.test.js.map +1 -1
  67. package/dist/src/conversation-manager/__tests__/null-conversation-manager.test.js +2 -2
  68. package/dist/src/conversation-manager/__tests__/null-conversation-manager.test.js.map +1 -1
  69. package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.js +8 -3
  70. package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.js.map +1 -1
  71. package/dist/src/conversation-manager/__tests__/summarizing-conversation-manager.test.js +1 -0
  72. package/dist/src/conversation-manager/__tests__/summarizing-conversation-manager.test.js.map +1 -1
  73. package/dist/src/errors.d.ts +11 -0
  74. package/dist/src/errors.d.ts.map +1 -1
  75. package/dist/src/errors.js +12 -0
  76. package/dist/src/errors.js.map +1 -1
  77. package/dist/src/hooks/__tests__/events.test.js +177 -70
  78. package/dist/src/hooks/__tests__/events.test.js.map +1 -1
  79. package/dist/src/hooks/__tests__/registry.test.js +16 -16
  80. package/dist/src/hooks/__tests__/registry.test.js.map +1 -1
  81. package/dist/src/hooks/events.d.ts +95 -25
  82. package/dist/src/hooks/events.d.ts.map +1 -1
  83. package/dist/src/hooks/events.js +98 -23
  84. package/dist/src/hooks/events.js.map +1 -1
  85. package/dist/src/index.d.ts +5 -4
  86. package/dist/src/index.d.ts.map +1 -1
  87. package/dist/src/index.js.map +1 -1
  88. package/dist/src/logging/__tests__/warn-once.test.d.ts +2 -0
  89. package/dist/src/logging/__tests__/warn-once.test.d.ts.map +1 -0
  90. package/dist/src/logging/__tests__/warn-once.test.js +30 -0
  91. package/dist/src/logging/__tests__/warn-once.test.js.map +1 -0
  92. package/dist/src/logging/warn-once.d.ts +13 -0
  93. package/dist/src/logging/warn-once.d.ts.map +1 -0
  94. package/dist/src/logging/warn-once.js +18 -0
  95. package/dist/src/logging/warn-once.js.map +1 -0
  96. package/dist/src/mcp.d.ts +20 -1
  97. package/dist/src/mcp.d.ts.map +1 -1
  98. package/dist/src/mcp.js +10 -1
  99. package/dist/src/mcp.js.map +1 -1
  100. package/dist/src/mime.d.ts +2 -1
  101. package/dist/src/mime.d.ts.map +1 -1
  102. package/dist/src/mime.js +1 -0
  103. package/dist/src/mime.js.map +1 -1
  104. package/dist/src/models/__tests__/anthropic.test.js +92 -3
  105. package/dist/src/models/__tests__/anthropic.test.js.map +1 -1
  106. package/dist/src/models/__tests__/bedrock.test.js +113 -2
  107. package/dist/src/models/__tests__/bedrock.test.js.map +1 -1
  108. package/dist/src/models/__tests__/google.test.js +77 -0
  109. package/dist/src/models/__tests__/google.test.js.map +1 -1
  110. package/dist/src/models/__tests__/model.test.js +149 -1
  111. package/dist/src/models/__tests__/model.test.js.map +1 -1
  112. package/dist/src/models/anthropic.d.ts +12 -1
  113. package/dist/src/models/anthropic.d.ts.map +1 -1
  114. package/dist/src/models/anthropic.js +38 -6
  115. package/dist/src/models/anthropic.js.map +1 -1
  116. package/dist/src/models/bedrock.d.ts +12 -1
  117. package/dist/src/models/bedrock.d.ts.map +1 -1
  118. package/dist/src/models/bedrock.js +45 -11
  119. package/dist/src/models/bedrock.js.map +1 -1
  120. package/dist/src/models/defaults.d.ts +37 -0
  121. package/dist/src/models/defaults.d.ts.map +1 -0
  122. package/dist/src/models/defaults.js +41 -0
  123. package/dist/src/models/defaults.js.map +1 -0
  124. package/dist/src/models/google/model.d.ts +14 -1
  125. package/dist/src/models/google/model.d.ts.map +1 -1
  126. package/dist/src/models/google/model.js +50 -6
  127. package/dist/src/models/google/model.js.map +1 -1
  128. package/dist/src/models/model.d.ts +50 -0
  129. package/dist/src/models/model.d.ts.map +1 -1
  130. package/dist/src/models/model.js +120 -0
  131. package/dist/src/models/model.js.map +1 -1
  132. package/dist/src/models/openai/__tests__/chat.test.d.ts +2 -0
  133. package/dist/src/models/openai/__tests__/chat.test.d.ts.map +1 -0
  134. package/dist/src/models/{__tests__/openai.test.js → openai/__tests__/chat.test.js} +72 -7
  135. package/dist/src/models/openai/__tests__/chat.test.js.map +1 -0
  136. package/dist/src/models/openai/__tests__/responses.test.d.ts +2 -0
  137. package/dist/src/models/openai/__tests__/responses.test.d.ts.map +1 -0
  138. package/dist/src/models/openai/__tests__/responses.test.js +668 -0
  139. package/dist/src/models/openai/__tests__/responses.test.js.map +1 -0
  140. package/dist/src/models/openai/chat-adapter.d.ts +33 -0
  141. package/dist/src/models/openai/chat-adapter.d.ts.map +1 -0
  142. package/dist/src/models/openai/chat-adapter.js +383 -0
  143. package/dist/src/models/openai/chat-adapter.js.map +1 -0
  144. package/dist/src/models/openai/errors.d.ts +16 -0
  145. package/dist/src/models/openai/errors.d.ts.map +1 -0
  146. package/dist/src/models/openai/errors.js +40 -0
  147. package/dist/src/models/openai/errors.js.map +1 -0
  148. package/dist/src/models/openai/formatting.d.ts +18 -0
  149. package/dist/src/models/openai/formatting.d.ts.map +1 -0
  150. package/dist/src/models/openai/formatting.js +38 -0
  151. package/dist/src/models/openai/formatting.js.map +1 -0
  152. package/dist/src/models/openai/index.d.ts +19 -0
  153. package/dist/src/models/openai/index.d.ts.map +1 -0
  154. package/dist/src/models/openai/index.js +18 -0
  155. package/dist/src/models/openai/index.js.map +1 -0
  156. package/dist/src/models/openai/model.d.ts +77 -0
  157. package/dist/src/models/openai/model.d.ts.map +1 -0
  158. package/dist/src/models/openai/model.js +211 -0
  159. package/dist/src/models/openai/model.js.map +1 -0
  160. package/dist/src/models/openai/responses-adapter.d.ts +78 -0
  161. package/dist/src/models/openai/responses-adapter.d.ts.map +1 -0
  162. package/dist/src/models/openai/responses-adapter.js +467 -0
  163. package/dist/src/models/openai/responses-adapter.js.map +1 -0
  164. package/dist/src/models/openai/types.d.ts +131 -0
  165. package/dist/src/models/openai/types.d.ts.map +1 -0
  166. package/dist/src/models/openai/types.js +5 -0
  167. package/dist/src/models/openai/types.js.map +1 -0
  168. package/dist/src/multiagent/__tests__/events.test.js +122 -28
  169. package/dist/src/multiagent/__tests__/events.test.js.map +1 -1
  170. package/dist/src/multiagent/__tests__/graph.invocation-state.test.d.ts +2 -0
  171. package/dist/src/multiagent/__tests__/graph.invocation-state.test.d.ts.map +1 -0
  172. package/dist/src/multiagent/__tests__/graph.invocation-state.test.js +95 -0
  173. package/dist/src/multiagent/__tests__/graph.invocation-state.test.js.map +1 -0
  174. package/dist/src/multiagent/__tests__/nodes.test.js +5 -2
  175. package/dist/src/multiagent/__tests__/nodes.test.js.map +1 -1
  176. package/dist/src/multiagent/__tests__/swarm.invocation-state.test.d.ts +2 -0
  177. package/dist/src/multiagent/__tests__/swarm.invocation-state.test.d.ts.map +1 -0
  178. package/dist/src/multiagent/__tests__/swarm.invocation-state.test.js +56 -0
  179. package/dist/src/multiagent/__tests__/swarm.invocation-state.test.js.map +1 -0
  180. package/dist/src/multiagent/events.d.ts +19 -1
  181. package/dist/src/multiagent/events.d.ts.map +1 -1
  182. package/dist/src/multiagent/events.js +18 -0
  183. package/dist/src/multiagent/events.js.map +1 -1
  184. package/dist/src/multiagent/graph.d.ts +5 -3
  185. package/dist/src/multiagent/graph.d.ts.map +1 -1
  186. package/dist/src/multiagent/graph.js +22 -15
  187. package/dist/src/multiagent/graph.js.map +1 -1
  188. package/dist/src/multiagent/index.d.ts +1 -1
  189. package/dist/src/multiagent/index.d.ts.map +1 -1
  190. package/dist/src/multiagent/multiagent.d.ts +16 -3
  191. package/dist/src/multiagent/multiagent.d.ts.map +1 -1
  192. package/dist/src/multiagent/nodes.d.ts +10 -3
  193. package/dist/src/multiagent/nodes.d.ts.map +1 -1
  194. package/dist/src/multiagent/nodes.js +28 -6
  195. package/dist/src/multiagent/nodes.js.map +1 -1
  196. package/dist/src/multiagent/swarm.d.ts +5 -3
  197. package/dist/src/multiagent/swarm.d.ts.map +1 -1
  198. package/dist/src/multiagent/swarm.js +22 -16
  199. package/dist/src/multiagent/swarm.js.map +1 -1
  200. package/dist/src/plugins/__tests__/registry.test.js +1 -1
  201. package/dist/src/plugins/__tests__/registry.test.js.map +1 -1
  202. package/dist/src/plugins/model-plugin.d.ts +20 -0
  203. package/dist/src/plugins/model-plugin.d.ts.map +1 -0
  204. package/dist/src/plugins/model-plugin.js +29 -0
  205. package/dist/src/plugins/model-plugin.js.map +1 -0
  206. package/dist/src/session/__tests__/session-manager.test.js +13 -11
  207. package/dist/src/session/__tests__/session-manager.test.js.map +1 -1
  208. package/dist/src/session/session-manager.d.ts.map +1 -1
  209. package/dist/src/session/session-manager.js +9 -0
  210. package/dist/src/session/session-manager.js.map +1 -1
  211. package/dist/src/telemetry/__tests__/meter.test.js +23 -0
  212. package/dist/src/telemetry/__tests__/meter.test.js.map +1 -1
  213. package/dist/src/telemetry/meter.d.ts +15 -0
  214. package/dist/src/telemetry/meter.d.ts.map +1 -1
  215. package/dist/src/telemetry/meter.js +14 -0
  216. package/dist/src/telemetry/meter.js.map +1 -1
  217. package/dist/src/tools/mcp-tool.d.ts +24 -3
  218. package/dist/src/tools/mcp-tool.d.ts.map +1 -1
  219. package/dist/src/tools/mcp-tool.js +103 -31
  220. package/dist/src/tools/mcp-tool.js.map +1 -1
  221. package/dist/src/tools/tool.d.ts +11 -1
  222. package/dist/src/tools/tool.d.ts.map +1 -1
  223. package/dist/src/tools/tool.js.map +1 -1
  224. package/dist/src/tsconfig.tsbuildinfo +1 -1
  225. package/dist/src/types/__tests__/agent.test.js +48 -0
  226. package/dist/src/types/__tests__/agent.test.js.map +1 -1
  227. package/dist/src/types/agent.d.ts +55 -6
  228. package/dist/src/types/agent.d.ts.map +1 -1
  229. package/dist/src/types/agent.js +22 -6
  230. package/dist/src/types/agent.js.map +1 -1
  231. package/dist/src/types/elicitation.d.ts +15 -0
  232. package/dist/src/types/elicitation.d.ts.map +1 -0
  233. package/dist/src/types/elicitation.js +2 -0
  234. package/dist/src/types/elicitation.js.map +1 -0
  235. package/dist/src/vended-plugins/skills/__tests__/agent-skills.test.node.js +9 -5
  236. package/dist/src/vended-plugins/skills/__tests__/agent-skills.test.node.js.map +1 -1
  237. package/dist/src/vended-tools/bash/__tests__/bash.test.node.js +1 -0
  238. package/dist/src/vended-tools/bash/__tests__/bash.test.node.js.map +1 -1
  239. package/dist/src/vended-tools/file-editor/__tests__/file-editor.test.node.js +1 -0
  240. package/dist/src/vended-tools/file-editor/__tests__/file-editor.test.node.js.map +1 -1
  241. package/dist/src/vended-tools/notebook/__tests__/notebook.test.js +1 -0
  242. package/dist/src/vended-tools/notebook/__tests__/notebook.test.js.map +1 -1
  243. package/package.json +9 -5
  244. package/dist/src/models/__tests__/openai.test.d.ts +0 -2
  245. package/dist/src/models/__tests__/openai.test.d.ts.map +0 -1
  246. package/dist/src/models/__tests__/openai.test.js.map +0 -1
  247. package/dist/src/models/openai.d.ts +0 -312
  248. package/dist/src/models/openai.d.ts.map +0 -1
  249. package/dist/src/models/openai.js +0 -789
  250. package/dist/src/models/openai.js.map +0 -1
@@ -0,0 +1,668 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import OpenAI from 'openai';
3
+ import { isNode } from '../../../__fixtures__/environment.js';
4
+ import { OpenAIModel } from '../index.js';
5
+ import { ContextWindowOverflowError, ModelThrottledError } from '../../../errors.js';
6
+ import { collectIterator } from '../../../__fixtures__/model-test-helpers.js';
7
+ import { Message, TextBlock, ToolUseBlock, ToolResultBlock } from '../../../types/messages.js';
8
+ import { ImageBlock, DocumentBlock } from '../../../types/media.js';
9
+ import { StateStore } from '../../../state-store.js';
10
+ import { logger } from '../../../logging/logger.js';
11
+ /**
12
+ * Build a mock OpenAI client whose `responses.create` returns the given async generator.
13
+ * The last request passed to `create` is captured on `capture.request`.
14
+ */
15
+ function createMockClient(streamGenerator, capture = {}) {
16
+ return {
17
+ responses: {
18
+ create: vi.fn(async (request) => {
19
+ capture.request = request;
20
+ return streamGenerator();
21
+ }),
22
+ },
23
+ };
24
+ }
25
+ // Mock the OpenAI SDK
26
+ vi.mock('openai', () => {
27
+ const mockConstructor = vi.fn(function () {
28
+ return {};
29
+ });
30
+ return {
31
+ default: mockConstructor,
32
+ };
33
+ });
34
+ describe("OpenAIModel (api: 'responses')", () => {
35
+ beforeEach(() => {
36
+ vi.clearAllMocks();
37
+ vi.restoreAllMocks();
38
+ if (isNode) {
39
+ vi.stubEnv('OPENAI_API_KEY', 'sk-test-env');
40
+ }
41
+ });
42
+ afterEach(() => {
43
+ vi.clearAllMocks();
44
+ if (isNode) {
45
+ vi.unstubAllEnvs();
46
+ }
47
+ });
48
+ describe('constructor', () => {
49
+ it('uses API key from constructor parameter', () => {
50
+ new OpenAIModel({ api: 'responses', modelId: 'gpt-4o', apiKey: 'sk-explicit' });
51
+ expect(OpenAI).toHaveBeenCalledWith(expect.objectContaining({ apiKey: 'sk-explicit' }));
52
+ });
53
+ if (isNode) {
54
+ it('uses API key from environment variable', () => {
55
+ vi.stubEnv('OPENAI_API_KEY', 'sk-from-env');
56
+ new OpenAIModel({ api: 'responses', modelId: 'gpt-4o' });
57
+ expect(OpenAI).toHaveBeenCalled();
58
+ });
59
+ }
60
+ it('throws error when no API key is available', () => {
61
+ if (isNode) {
62
+ vi.stubEnv('OPENAI_API_KEY', '');
63
+ }
64
+ expect(() => new OpenAIModel({ api: 'responses', modelId: 'gpt-4o' })).toThrow(/OpenAI API key is required/);
65
+ });
66
+ it('uses provided client instance and skips OpenAI constructor', () => {
67
+ vi.clearAllMocks();
68
+ const client = {};
69
+ const model = new OpenAIModel({ api: 'responses', client });
70
+ expect(OpenAI).not.toHaveBeenCalled();
71
+ expect(model).toBeDefined();
72
+ });
73
+ it('does not require API key when client is provided', () => {
74
+ if (isNode) {
75
+ vi.stubEnv('OPENAI_API_KEY', '');
76
+ }
77
+ const client = {};
78
+ expect(() => new OpenAIModel({ api: 'responses', client })).not.toThrow();
79
+ });
80
+ });
81
+ describe('stateful', () => {
82
+ it('defaults to false', () => {
83
+ const model = new OpenAIModel({ api: 'responses', client: {} });
84
+ expect(model.stateful).toBe(false);
85
+ });
86
+ it('returns true when explicitly enabled', () => {
87
+ const model = new OpenAIModel({ api: 'responses', client: {}, stateful: true });
88
+ expect(model.stateful).toBe(true);
89
+ });
90
+ it('is construction-only and cannot be changed via updateConfig', () => {
91
+ const model = new OpenAIModel({ api: 'responses', client: {}, stateful: false });
92
+ const warnSpy = vi.spyOn(logger, 'warn');
93
+ expect(model.stateful).toBe(false);
94
+ model.updateConfig({ stateful: true });
95
+ expect(model.stateful).toBe(false);
96
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("'stateful' is construction-only"));
97
+ warnSpy.mockRestore();
98
+ });
99
+ });
100
+ describe('updateConfig / getConfig', () => {
101
+ it('merges config without clobbering unspecified fields', () => {
102
+ const model = new OpenAIModel({
103
+ api: 'responses',
104
+ client: {},
105
+ modelId: 'gpt-4o',
106
+ temperature: 0.5,
107
+ maxTokens: 1024,
108
+ });
109
+ model.updateConfig({ temperature: 0.9 });
110
+ expect(model.getConfig()).toMatchObject({
111
+ modelId: 'gpt-4o',
112
+ temperature: 0.9,
113
+ maxTokens: 1024,
114
+ });
115
+ });
116
+ });
117
+ describe('managed params warning', () => {
118
+ it('warns on construction when params contains provider-managed keys', () => {
119
+ const warnSpy = vi.spyOn(logger, 'warn');
120
+ new OpenAIModel({ api: 'responses', client: {}, params: { model: 'bad', store: false } });
121
+ expect(warnSpy).toHaveBeenCalledTimes(2);
122
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("'model'"));
123
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("'store'"));
124
+ warnSpy.mockRestore();
125
+ });
126
+ it('warns on updateConfig when params contains provider-managed keys', () => {
127
+ const model = new OpenAIModel({ api: 'responses', client: {} });
128
+ const warnSpy = vi.spyOn(logger, 'warn');
129
+ model.updateConfig({ params: { stream: true } });
130
+ expect(warnSpy).toHaveBeenCalledTimes(1);
131
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("'stream'"));
132
+ warnSpy.mockRestore();
133
+ });
134
+ it('does not warn when params contains only non-managed keys', () => {
135
+ const warnSpy = vi.spyOn(logger, 'warn');
136
+ new OpenAIModel({ api: 'responses', client: {}, params: { reasoning: { summary: 'auto' } } });
137
+ expect(warnSpy).not.toHaveBeenCalled();
138
+ warnSpy.mockRestore();
139
+ });
140
+ });
141
+ describe('request formatting', () => {
142
+ const mkUserMessage = () => new Message({ role: 'user', content: [new TextBlock('Hi')] });
143
+ async function runOnce(modelOptions = {}, messages = [mkUserMessage()], streamOptions = undefined) {
144
+ const capture = {};
145
+ const client = createMockClient(async function* () {
146
+ yield { type: 'response.created', response: { id: 'resp_123' } };
147
+ yield { type: 'response.completed', response: { usage: undefined } };
148
+ }, capture);
149
+ const model = new OpenAIModel({ api: 'responses', client, ...modelOptions });
150
+ await collectIterator(model.stream(messages, streamOptions));
151
+ return capture.request;
152
+ }
153
+ it('includes model, input, stream, and store=false by default', async () => {
154
+ const req = await runOnce();
155
+ expect(req.model).toBe('gpt-5.4');
156
+ expect(req.stream).toBe(true);
157
+ expect(req.store).toBe(false);
158
+ expect(Array.isArray(req.input)).toBe(true);
159
+ });
160
+ it('sets store=true when stateful is enabled', async () => {
161
+ const req = await runOnce({ stateful: true });
162
+ expect(req.store).toBe(true);
163
+ });
164
+ it('chains previous_response_id when stateful and modelState has responseId', async () => {
165
+ const modelState = new StateStore({ responseId: 'resp_prev' });
166
+ const req = await runOnce({ stateful: true }, [mkUserMessage()], { modelState });
167
+ expect(req.previous_response_id).toBe('resp_prev');
168
+ });
169
+ it('omits previous_response_id when stateful is disabled, even with responseId in modelState', async () => {
170
+ const modelState = new StateStore({ responseId: 'resp_prev' });
171
+ const req = await runOnce({}, [mkUserMessage()], { modelState });
172
+ expect(req.previous_response_id).toBeUndefined();
173
+ });
174
+ it('maps systemPrompt string to instructions', async () => {
175
+ const req = await runOnce({}, [mkUserMessage()], { systemPrompt: 'Be helpful.' });
176
+ expect(req.instructions).toBe('Be helpful.');
177
+ });
178
+ it('merges toolSpecs with built-in tools from params', async () => {
179
+ const req = await runOnce({ params: { tools: [{ type: 'web_search' }] } }, [mkUserMessage()], {
180
+ toolSpecs: [
181
+ {
182
+ name: 'calc',
183
+ description: 'calculator',
184
+ inputSchema: { type: 'object', properties: {} },
185
+ },
186
+ ],
187
+ });
188
+ expect(req.tools).toEqual([
189
+ { type: 'web_search' },
190
+ {
191
+ type: 'function',
192
+ name: 'calc',
193
+ description: 'calculator',
194
+ parameters: { type: 'object', properties: {} },
195
+ strict: null,
196
+ },
197
+ ]);
198
+ });
199
+ it('maps tool_choice variants', async () => {
200
+ const toolSpecs = [{ name: 'calc', description: 'd', inputSchema: {} }];
201
+ const autoReq = await runOnce({}, [mkUserMessage()], { toolSpecs, toolChoice: { auto: {} } });
202
+ expect(autoReq.tool_choice).toBe('auto');
203
+ const anyReq = await runOnce({}, [mkUserMessage()], { toolSpecs, toolChoice: { any: {} } });
204
+ expect(anyReq.tool_choice).toBe('required');
205
+ const toolReq = await runOnce({}, [mkUserMessage()], {
206
+ toolSpecs,
207
+ toolChoice: { tool: { name: 'calc' } },
208
+ });
209
+ expect(toolReq.tool_choice).toEqual({ type: 'function', name: 'calc' });
210
+ });
211
+ it('formats temperature, maxTokens→max_output_tokens, and topP', async () => {
212
+ const req = await runOnce({ temperature: 0.3, maxTokens: 512, topP: 0.8 });
213
+ expect(req.temperature).toBe(0.3);
214
+ expect(req.max_output_tokens).toBe(512);
215
+ expect(req.top_p).toBe(0.8);
216
+ });
217
+ it('passes through extra params fields to the request', async () => {
218
+ const req = await runOnce({ params: { reasoning: { summary: 'auto' } } });
219
+ expect(req.reasoning).toEqual({ summary: 'auto' });
220
+ });
221
+ it('provider-managed fields in params are overridden and cannot take effect', async () => {
222
+ const warnSpy = vi.spyOn(logger, 'warn');
223
+ const req = await runOnce({
224
+ modelId: 'gpt-4o',
225
+ stateful: true,
226
+ params: { model: 'attacker-model', input: 'hijacked', stream: false, store: false },
227
+ });
228
+ expect(req.model).toBe('gpt-4o');
229
+ expect(req.stream).toBe(true);
230
+ expect(req.store).toBe(true);
231
+ expect(Array.isArray(req.input)).toBe(true);
232
+ warnSpy.mockRestore();
233
+ });
234
+ it('emits tool_use and tool_result as separate top-level items', async () => {
235
+ const messages = [
236
+ new Message({ role: 'user', content: [new TextBlock('run it')] }),
237
+ new Message({
238
+ role: 'assistant',
239
+ content: [new ToolUseBlock({ name: 'calc', toolUseId: 'call_1', input: { expr: '2+2' } })],
240
+ }),
241
+ new Message({
242
+ role: 'user',
243
+ content: [
244
+ new ToolResultBlock({
245
+ toolUseId: 'call_1',
246
+ status: 'success',
247
+ content: [new TextBlock('4')],
248
+ }),
249
+ ],
250
+ }),
251
+ ];
252
+ const req = await runOnce({}, messages);
253
+ const functionCall = req.input.find((i) => i.type === 'function_call');
254
+ const functionOutput = req.input.find((i) => i.type === 'function_call_output');
255
+ expect(functionCall).toMatchObject({
256
+ type: 'function_call',
257
+ call_id: 'call_1',
258
+ name: 'calc',
259
+ arguments: JSON.stringify({ expr: '2+2' }),
260
+ });
261
+ expect(functionOutput).toMatchObject({
262
+ type: 'function_call_output',
263
+ call_id: 'call_1',
264
+ output: '4',
265
+ });
266
+ });
267
+ it('prefixes errored tool results with [ERROR]', async () => {
268
+ const messages = [
269
+ new Message({ role: 'user', content: [new TextBlock('x')] }),
270
+ new Message({
271
+ role: 'user',
272
+ content: [
273
+ new ToolResultBlock({
274
+ toolUseId: 't1',
275
+ status: 'error',
276
+ content: [new TextBlock('boom')],
277
+ }),
278
+ ],
279
+ }),
280
+ ];
281
+ const req = await runOnce({}, messages);
282
+ const out = req.input.find((i) => i.type === 'function_call_output');
283
+ expect(out.output).toBe('[ERROR] boom');
284
+ });
285
+ it('emits an array output with input_image when a tool result carries image bytes', async () => {
286
+ const imageBytes = new Uint8Array([1, 2, 3, 4]);
287
+ const messages = [
288
+ new Message({ role: 'user', content: [new TextBlock('fetch')] }),
289
+ new Message({
290
+ role: 'user',
291
+ content: [
292
+ new ToolResultBlock({
293
+ toolUseId: 'img_tool',
294
+ status: 'success',
295
+ content: [
296
+ new TextBlock('here is the image'),
297
+ new ImageBlock({ format: 'png', source: { bytes: imageBytes } }),
298
+ ],
299
+ }),
300
+ ],
301
+ }),
302
+ ];
303
+ const req = await runOnce({}, messages);
304
+ const out = req.input.find((i) => i.type === 'function_call_output');
305
+ expect(Array.isArray(out.output)).toBe(true);
306
+ expect(out.output).toEqual([
307
+ { type: 'input_text', text: 'here is the image' },
308
+ { type: 'input_image', image_url: expect.stringMatching(/^data:image\/png;base64,/) },
309
+ ]);
310
+ });
311
+ it('emits an array output with input_file when a tool result carries a document', async () => {
312
+ const docBytes = new Uint8Array([5, 6, 7, 8]);
313
+ const messages = [
314
+ new Message({ role: 'user', content: [new TextBlock('read')] }),
315
+ new Message({
316
+ role: 'user',
317
+ content: [
318
+ new ToolResultBlock({
319
+ toolUseId: 'doc_tool',
320
+ status: 'success',
321
+ content: [new DocumentBlock({ name: 'report.pdf', format: 'pdf', source: { bytes: docBytes } })],
322
+ }),
323
+ ],
324
+ }),
325
+ ];
326
+ const req = await runOnce({}, messages);
327
+ const out = req.input.find((i) => i.type === 'function_call_output');
328
+ expect(Array.isArray(out.output)).toBe(true);
329
+ expect(out.output).toEqual([
330
+ {
331
+ type: 'input_file',
332
+ file_data: expect.stringMatching(/^data:application\/pdf;base64,/),
333
+ filename: 'report.pdf',
334
+ },
335
+ ]);
336
+ });
337
+ it('keeps tool result output as a plain string when only text is present', async () => {
338
+ const messages = [
339
+ new Message({ role: 'user', content: [new TextBlock('ping')] }),
340
+ new Message({
341
+ role: 'user',
342
+ content: [
343
+ new ToolResultBlock({
344
+ toolUseId: 'text_tool',
345
+ status: 'success',
346
+ content: [new TextBlock('pong')],
347
+ }),
348
+ ],
349
+ }),
350
+ ];
351
+ const req = await runOnce({}, messages);
352
+ const out = req.input.find((i) => i.type === 'function_call_output');
353
+ expect(typeof out.output).toBe('string');
354
+ expect(out.output).toBe('pong');
355
+ });
356
+ });
357
+ describe('stream event mapping', () => {
358
+ it('captures responseId on response.created when stateful', async () => {
359
+ const modelState = new StateStore();
360
+ const client = createMockClient(async function* () {
361
+ yield { type: 'response.created', response: { id: 'resp_abc' } };
362
+ yield { type: 'response.completed', response: {} };
363
+ });
364
+ const model = new OpenAIModel({ api: 'responses', client, stateful: true });
365
+ await collectIterator(model.stream([new Message({ role: 'user', content: [new TextBlock('hi')] })], { modelState }));
366
+ expect(modelState.get('responseId')).toBe('resp_abc');
367
+ });
368
+ it('does NOT capture responseId when stateful is disabled', async () => {
369
+ const modelState = new StateStore();
370
+ const client = createMockClient(async function* () {
371
+ yield { type: 'response.created', response: { id: 'resp_abc' } };
372
+ yield { type: 'response.completed', response: {} };
373
+ });
374
+ const model = new OpenAIModel({ api: 'responses', client });
375
+ await collectIterator(model.stream([new Message({ role: 'user', content: [new TextBlock('hi')] })], { modelState }));
376
+ expect(modelState.get('responseId')).toBeUndefined();
377
+ });
378
+ it('emits text deltas inside a content block', async () => {
379
+ const client = createMockClient(async function* () {
380
+ yield { type: 'response.created', response: { id: 'r' } };
381
+ yield { type: 'response.output_text.delta', delta: 'Hello' };
382
+ yield { type: 'response.output_text.delta', delta: ' world' };
383
+ yield { type: 'response.completed', response: {} };
384
+ });
385
+ const model = new OpenAIModel({ api: 'responses', client });
386
+ const events = await collectIterator(model.stream([new Message({ role: 'user', content: [new TextBlock('x')] })]));
387
+ const types = events.map((e) => e.type);
388
+ expect(types).toEqual([
389
+ 'modelMessageStartEvent',
390
+ 'modelContentBlockStartEvent',
391
+ 'modelContentBlockDeltaEvent',
392
+ 'modelContentBlockDeltaEvent',
393
+ 'modelContentBlockStopEvent',
394
+ 'modelMessageStopEvent',
395
+ ]);
396
+ const deltas = events.filter((e) => e.type === 'modelContentBlockDeltaEvent').map((e) => e.delta);
397
+ expect(deltas).toEqual([
398
+ { type: 'textDelta', text: 'Hello' },
399
+ { type: 'textDelta', text: ' world' },
400
+ ]);
401
+ });
402
+ it('switches content blocks between reasoning and text', async () => {
403
+ const client = createMockClient(async function* () {
404
+ yield { type: 'response.created', response: { id: 'r' } };
405
+ yield { type: 'response.reasoning_text.delta', delta: 'thinking...' };
406
+ yield { type: 'response.output_text.delta', delta: 'answer' };
407
+ yield { type: 'response.completed', response: {} };
408
+ });
409
+ const model = new OpenAIModel({ api: 'responses', client });
410
+ const events = await collectIterator(model.stream([new Message({ role: 'user', content: [new TextBlock('x')] })]));
411
+ const types = events.map((e) => e.type);
412
+ expect(types).toEqual([
413
+ 'modelMessageStartEvent',
414
+ 'modelContentBlockStartEvent',
415
+ 'modelContentBlockDeltaEvent', // reasoning
416
+ 'modelContentBlockStopEvent',
417
+ 'modelContentBlockStartEvent',
418
+ 'modelContentBlockDeltaEvent', // text
419
+ 'modelContentBlockStopEvent',
420
+ 'modelMessageStopEvent',
421
+ ]);
422
+ });
423
+ it('emits tool call triplet after stream close and sets stopReason=toolUse', async () => {
424
+ const client = createMockClient(async function* () {
425
+ yield { type: 'response.created', response: { id: 'r' } };
426
+ yield {
427
+ type: 'response.output_item.added',
428
+ item: { type: 'function_call', id: 'item_1', call_id: 'call_1', name: 'calc' },
429
+ };
430
+ yield {
431
+ type: 'response.function_call_arguments.delta',
432
+ item_id: 'item_1',
433
+ delta: '{"a":',
434
+ };
435
+ yield {
436
+ type: 'response.function_call_arguments.delta',
437
+ item_id: 'item_1',
438
+ delta: '1}',
439
+ };
440
+ yield {
441
+ type: 'response.function_call_arguments.done',
442
+ item_id: 'item_1',
443
+ arguments: '{"a":1}',
444
+ };
445
+ yield { type: 'response.completed', response: {} };
446
+ });
447
+ const model = new OpenAIModel({ api: 'responses', client });
448
+ const events = await collectIterator(model.stream([new Message({ role: 'user', content: [new TextBlock('x')] })]));
449
+ const startEvent = events.find((e) => e.type === 'modelContentBlockStartEvent' && e.start?.type === 'toolUseStart');
450
+ expect(startEvent?.start).toEqual({
451
+ type: 'toolUseStart',
452
+ name: 'calc',
453
+ toolUseId: 'call_1',
454
+ });
455
+ const deltaEvent = events.find((e) => e.type === 'modelContentBlockDeltaEvent' && e.delta?.type === 'toolUseInputDelta');
456
+ expect(deltaEvent?.delta).toEqual({ type: 'toolUseInputDelta', input: '{"a":1}' });
457
+ const stopEvent = events.find((e) => e.type === 'modelMessageStopEvent');
458
+ expect(stopEvent?.stopReason).toBe('toolUse');
459
+ });
460
+ it('maps response.incomplete with max_output_tokens to stopReason=maxTokens', async () => {
461
+ const client = createMockClient(async function* () {
462
+ yield { type: 'response.created', response: { id: 'r' } };
463
+ yield { type: 'response.output_text.delta', delta: 'partial' };
464
+ yield {
465
+ type: 'response.incomplete',
466
+ response: {
467
+ incomplete_details: { reason: 'max_output_tokens' },
468
+ usage: { input_tokens: 10, output_tokens: 5, total_tokens: 15 },
469
+ },
470
+ };
471
+ });
472
+ const model = new OpenAIModel({ api: 'responses', client });
473
+ const events = await collectIterator(model.stream([new Message({ role: 'user', content: [new TextBlock('x')] })]));
474
+ const stop = events.find((e) => e.type === 'modelMessageStopEvent');
475
+ expect(stop?.stopReason).toBe('maxTokens');
476
+ const metadata = events.find((e) => e.type === 'modelMetadataEvent');
477
+ expect(metadata?.usage).toEqual({ inputTokens: 10, outputTokens: 5, totalTokens: 15 });
478
+ });
479
+ it('emits URL citation delta from response.output_text.annotation.added', async () => {
480
+ const client = createMockClient(async function* () {
481
+ yield { type: 'response.created', response: { id: 'r' } };
482
+ yield { type: 'response.output_text.delta', delta: 'The answer is here.' };
483
+ yield {
484
+ type: 'response.output_text.annotation.added',
485
+ annotation: {
486
+ type: 'url_citation',
487
+ url: 'https://example.com',
488
+ title: 'Example',
489
+ cited_text: 'here',
490
+ },
491
+ };
492
+ yield { type: 'response.completed', response: {} };
493
+ });
494
+ const model = new OpenAIModel({ api: 'responses', client });
495
+ const events = await collectIterator(model.stream([new Message({ role: 'user', content: [new TextBlock('x')] })]));
496
+ const citation = events.find((e) => e.type === 'modelContentBlockDeltaEvent' && e.delta?.type === 'citationsDelta');
497
+ expect(citation?.delta.citations[0]).toMatchObject({
498
+ location: { type: 'web', url: 'https://example.com' },
499
+ source: 'https://example.com',
500
+ title: 'Example',
501
+ });
502
+ });
503
+ it('closes the text block before a citation, producing separate blocks when stream ends after citation', async () => {
504
+ const client = createMockClient(async function* () {
505
+ yield { type: 'response.created', response: { id: 'r' } };
506
+ yield { type: 'response.output_text.delta', delta: 'Before citation' };
507
+ yield {
508
+ type: 'response.output_text.annotation.added',
509
+ annotation: {
510
+ type: 'url_citation',
511
+ url: 'https://example.com',
512
+ title: 'Source',
513
+ cited_text: 'cited',
514
+ },
515
+ };
516
+ yield { type: 'response.completed', response: {} };
517
+ });
518
+ const model = new OpenAIModel({ api: 'responses', client });
519
+ const events = await collectIterator(model.stream([new Message({ role: 'user', content: [new TextBlock('x')] })]));
520
+ const types = events.map((e) => e.type);
521
+ expect(types).toEqual([
522
+ 'modelMessageStartEvent',
523
+ // Text block — closed before citation
524
+ 'modelContentBlockStartEvent',
525
+ 'modelContentBlockDeltaEvent',
526
+ 'modelContentBlockStopEvent',
527
+ // Citation block
528
+ 'modelContentBlockStartEvent',
529
+ 'modelContentBlockDeltaEvent',
530
+ 'modelContentBlockStopEvent',
531
+ 'modelMessageStopEvent',
532
+ ]);
533
+ const deltas = events.filter((e) => e.type === 'modelContentBlockDeltaEvent').map((e) => e.delta.type);
534
+ expect(deltas).toEqual(['textDelta', 'citationsDelta']);
535
+ });
536
+ it('closes the text block before a citation and opens a new text block after', async () => {
537
+ const client = createMockClient(async function* () {
538
+ yield { type: 'response.created', response: { id: 'r' } };
539
+ yield { type: 'response.output_text.delta', delta: 'Before ' };
540
+ yield {
541
+ type: 'response.output_text.annotation.added',
542
+ annotation: {
543
+ type: 'url_citation',
544
+ url: 'https://example.com',
545
+ title: 'Source',
546
+ cited_text: 'cited',
547
+ },
548
+ };
549
+ yield { type: 'response.output_text.delta', delta: ' after' };
550
+ yield { type: 'response.completed', response: {} };
551
+ });
552
+ const model = new OpenAIModel({ api: 'responses', client });
553
+ const events = await collectIterator(model.stream([new Message({ role: 'user', content: [new TextBlock('x')] })]));
554
+ const types = events.map((e) => e.type);
555
+ expect(types).toEqual([
556
+ 'modelMessageStartEvent',
557
+ // First text block
558
+ 'modelContentBlockStartEvent',
559
+ 'modelContentBlockDeltaEvent',
560
+ 'modelContentBlockStopEvent',
561
+ // Citation block
562
+ 'modelContentBlockStartEvent',
563
+ 'modelContentBlockDeltaEvent',
564
+ 'modelContentBlockStopEvent',
565
+ // New text block after citation
566
+ 'modelContentBlockStartEvent',
567
+ 'modelContentBlockDeltaEvent',
568
+ 'modelContentBlockStopEvent',
569
+ 'modelMessageStopEvent',
570
+ ]);
571
+ const deltas = events.filter((e) => e.type === 'modelContentBlockDeltaEvent').map((e) => e.delta.type);
572
+ expect(deltas).toEqual(['textDelta', 'citationsDelta', 'textDelta']);
573
+ });
574
+ it('keeps consecutive citations in the same block without extra stop/start', async () => {
575
+ const client = createMockClient(async function* () {
576
+ yield { type: 'response.created', response: { id: 'r' } };
577
+ yield {
578
+ type: 'response.output_text.annotation.added',
579
+ annotation: { type: 'url_citation', url: 'https://a.com', title: 'A', cited_text: 'a' },
580
+ };
581
+ yield {
582
+ type: 'response.output_text.annotation.added',
583
+ annotation: { type: 'url_citation', url: 'https://b.com', title: 'B', cited_text: 'b' },
584
+ };
585
+ yield { type: 'response.completed', response: {} };
586
+ });
587
+ const model = new OpenAIModel({ api: 'responses', client });
588
+ const events = await collectIterator(model.stream([new Message({ role: 'user', content: [new TextBlock('x')] })]));
589
+ const types = events.map((e) => e.type);
590
+ expect(types).toEqual([
591
+ 'modelMessageStartEvent',
592
+ 'modelContentBlockStartEvent',
593
+ 'modelContentBlockDeltaEvent',
594
+ 'modelContentBlockDeltaEvent',
595
+ 'modelContentBlockStopEvent',
596
+ 'modelMessageStopEvent',
597
+ ]);
598
+ });
599
+ it('handles text → citation → text → citation → text with separate blocks each time', async () => {
600
+ const client = createMockClient(async function* () {
601
+ yield { type: 'response.created', response: { id: 'r' } };
602
+ yield { type: 'response.output_text.delta', delta: 'intro ' };
603
+ yield {
604
+ type: 'response.output_text.annotation.added',
605
+ annotation: { type: 'url_citation', url: 'https://1.com', title: '1', cited_text: 'c1' },
606
+ };
607
+ yield { type: 'response.output_text.delta', delta: 'middle ' };
608
+ yield {
609
+ type: 'response.output_text.annotation.added',
610
+ annotation: { type: 'url_citation', url: 'https://2.com', title: '2', cited_text: 'c2' },
611
+ };
612
+ yield { type: 'response.output_text.delta', delta: 'end' };
613
+ yield { type: 'response.completed', response: {} };
614
+ });
615
+ const model = new OpenAIModel({ api: 'responses', client });
616
+ const events = await collectIterator(model.stream([new Message({ role: 'user', content: [new TextBlock('x')] })]));
617
+ const deltaTypes = events
618
+ .filter((e) => e.type === 'modelContentBlockDeltaEvent')
619
+ .map((e) => e.delta.type);
620
+ expect(deltaTypes).toEqual(['textDelta', 'citationsDelta', 'textDelta', 'citationsDelta', 'textDelta']);
621
+ // 5 content blocks = 5 start + 5 stop events
622
+ const starts = events.filter((e) => e.type === 'modelContentBlockStartEvent');
623
+ const stops = events.filter((e) => e.type === 'modelContentBlockStopEvent');
624
+ expect(starts).toHaveLength(5);
625
+ expect(stops).toHaveLength(5);
626
+ });
627
+ });
628
+ describe('error mapping', () => {
629
+ it('wraps 429 as ModelThrottledError', async () => {
630
+ const client = {
631
+ responses: {
632
+ create: vi.fn(async () => {
633
+ const err = new Error('Too many requests');
634
+ err.status = 429;
635
+ throw err;
636
+ }),
637
+ },
638
+ };
639
+ const model = new OpenAIModel({ api: 'responses', client });
640
+ await expect(collectIterator(model.stream([new Message({ role: 'user', content: [new TextBlock('x')] })]))).rejects.toBeInstanceOf(ModelThrottledError);
641
+ });
642
+ it('wraps context_length_exceeded as ContextWindowOverflowError', async () => {
643
+ const client = {
644
+ responses: {
645
+ create: vi.fn(async () => {
646
+ const err = new Error('This model has a maximum context length of 8k.');
647
+ err.code = 'context_length_exceeded';
648
+ throw err;
649
+ }),
650
+ },
651
+ };
652
+ const model = new OpenAIModel({ api: 'responses', client });
653
+ await expect(collectIterator(model.stream([new Message({ role: 'user', content: [new TextBlock('x')] })]))).rejects.toBeInstanceOf(ContextWindowOverflowError);
654
+ });
655
+ it('rethrows unknown errors untouched', async () => {
656
+ const client = {
657
+ responses: {
658
+ create: vi.fn(async () => {
659
+ throw new Error('some other failure');
660
+ }),
661
+ },
662
+ };
663
+ const model = new OpenAIModel({ api: 'responses', client });
664
+ await expect(collectIterator(model.stream([new Message({ role: 'user', content: [new TextBlock('x')] })]))).rejects.toThrow('some other failure');
665
+ });
666
+ });
667
+ });
668
+ //# sourceMappingURL=responses.test.js.map