@steipete/summarize 0.8.2 → 0.10.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 (284) hide show
  1. package/CHANGELOG.md +114 -1
  2. package/LICENSE +1 -1
  3. package/README.md +309 -182
  4. package/dist/cli.js +1 -1
  5. package/dist/esm/cache.js +72 -4
  6. package/dist/esm/cache.js.map +1 -1
  7. package/dist/esm/config.js +197 -1
  8. package/dist/esm/config.js.map +1 -1
  9. package/dist/esm/content/asset.js +75 -2
  10. package/dist/esm/content/asset.js.map +1 -1
  11. package/dist/esm/daemon/agent.js +547 -0
  12. package/dist/esm/daemon/agent.js.map +1 -0
  13. package/dist/esm/daemon/chat.js +97 -0
  14. package/dist/esm/daemon/chat.js.map +1 -0
  15. package/dist/esm/daemon/cli.js +105 -10
  16. package/dist/esm/daemon/cli.js.map +1 -1
  17. package/dist/esm/daemon/env-snapshot.js +3 -0
  18. package/dist/esm/daemon/env-snapshot.js.map +1 -1
  19. package/dist/esm/daemon/flow-context.js +53 -28
  20. package/dist/esm/daemon/flow-context.js.map +1 -1
  21. package/dist/esm/daemon/launchd.js +27 -0
  22. package/dist/esm/daemon/launchd.js.map +1 -1
  23. package/dist/esm/daemon/process-registry.js +206 -0
  24. package/dist/esm/daemon/process-registry.js.map +1 -0
  25. package/dist/esm/daemon/schtasks.js +64 -0
  26. package/dist/esm/daemon/schtasks.js.map +1 -1
  27. package/dist/esm/daemon/server.js +1034 -52
  28. package/dist/esm/daemon/server.js.map +1 -1
  29. package/dist/esm/daemon/summarize.js +66 -18
  30. package/dist/esm/daemon/summarize.js.map +1 -1
  31. package/dist/esm/daemon/systemd.js +61 -0
  32. package/dist/esm/daemon/systemd.js.map +1 -1
  33. package/dist/esm/flags.js +24 -0
  34. package/dist/esm/flags.js.map +1 -1
  35. package/dist/esm/llm/attachments.js +2 -0
  36. package/dist/esm/llm/attachments.js.map +1 -0
  37. package/dist/esm/llm/errors.js +6 -0
  38. package/dist/esm/llm/errors.js.map +1 -0
  39. package/dist/esm/llm/generate-text.js +206 -356
  40. package/dist/esm/llm/generate-text.js.map +1 -1
  41. package/dist/esm/llm/html-to-markdown.js +1 -2
  42. package/dist/esm/llm/html-to-markdown.js.map +1 -1
  43. package/dist/esm/llm/prompt.js.map +1 -1
  44. package/dist/esm/llm/providers/anthropic.js +126 -0
  45. package/dist/esm/llm/providers/anthropic.js.map +1 -0
  46. package/dist/esm/llm/providers/google.js +78 -0
  47. package/dist/esm/llm/providers/google.js.map +1 -0
  48. package/dist/esm/llm/providers/models.js +111 -0
  49. package/dist/esm/llm/providers/models.js.map +1 -0
  50. package/dist/esm/llm/providers/openai.js +150 -0
  51. package/dist/esm/llm/providers/openai.js.map +1 -0
  52. package/dist/esm/llm/providers/shared.js +48 -0
  53. package/dist/esm/llm/providers/shared.js.map +1 -0
  54. package/dist/esm/llm/providers/types.js +2 -0
  55. package/dist/esm/llm/providers/types.js.map +1 -0
  56. package/dist/esm/llm/transcript-to-markdown.js +1 -2
  57. package/dist/esm/llm/transcript-to-markdown.js.map +1 -1
  58. package/dist/esm/llm/types.js +2 -0
  59. package/dist/esm/llm/types.js.map +1 -0
  60. package/dist/esm/llm/usage.js +69 -0
  61. package/dist/esm/llm/usage.js.map +1 -0
  62. package/dist/esm/logging/daemon.js +124 -0
  63. package/dist/esm/logging/daemon.js.map +1 -0
  64. package/dist/esm/logging/ring-file.js +66 -0
  65. package/dist/esm/logging/ring-file.js.map +1 -0
  66. package/dist/esm/media-cache.js +251 -0
  67. package/dist/esm/media-cache.js.map +1 -0
  68. package/dist/esm/model-auto.js +103 -5
  69. package/dist/esm/model-auto.js.map +1 -1
  70. package/dist/esm/processes.js +2 -0
  71. package/dist/esm/processes.js.map +1 -0
  72. package/dist/esm/refresh-free.js +3 -3
  73. package/dist/esm/refresh-free.js.map +1 -1
  74. package/dist/esm/run/attachments.js +8 -4
  75. package/dist/esm/run/attachments.js.map +1 -1
  76. package/dist/esm/run/bird.js +118 -5
  77. package/dist/esm/run/bird.js.map +1 -1
  78. package/dist/esm/run/cache-state.js +3 -2
  79. package/dist/esm/run/cache-state.js.map +1 -1
  80. package/dist/esm/run/cli-preflight.js +19 -1
  81. package/dist/esm/run/cli-preflight.js.map +1 -1
  82. package/dist/esm/run/constants.js +0 -7
  83. package/dist/esm/run/constants.js.map +1 -1
  84. package/dist/esm/run/finish-line.js +58 -11
  85. package/dist/esm/run/finish-line.js.map +1 -1
  86. package/dist/esm/run/flows/asset/extract.js +70 -0
  87. package/dist/esm/run/flows/asset/extract.js.map +1 -0
  88. package/dist/esm/run/flows/asset/input.js +209 -25
  89. package/dist/esm/run/flows/asset/input.js.map +1 -1
  90. package/dist/esm/run/flows/asset/media-policy.js +3 -0
  91. package/dist/esm/run/flows/asset/media-policy.js.map +1 -0
  92. package/dist/esm/run/flows/asset/media.js +224 -0
  93. package/dist/esm/run/flows/asset/media.js.map +1 -0
  94. package/dist/esm/run/flows/asset/output.js +98 -0
  95. package/dist/esm/run/flows/asset/output.js.map +1 -0
  96. package/dist/esm/run/flows/asset/preprocess.js +92 -16
  97. package/dist/esm/run/flows/asset/preprocess.js.map +1 -1
  98. package/dist/esm/run/flows/asset/summary.js +165 -11
  99. package/dist/esm/run/flows/asset/summary.js.map +1 -1
  100. package/dist/esm/run/flows/url/extract.js +6 -6
  101. package/dist/esm/run/flows/url/extract.js.map +1 -1
  102. package/dist/esm/run/flows/url/flow.js +338 -36
  103. package/dist/esm/run/flows/url/flow.js.map +1 -1
  104. package/dist/esm/run/flows/url/markdown.js +6 -1
  105. package/dist/esm/run/flows/url/markdown.js.map +1 -1
  106. package/dist/esm/run/flows/url/slides-output.js +485 -0
  107. package/dist/esm/run/flows/url/slides-output.js.map +1 -0
  108. package/dist/esm/run/flows/url/slides-text.js +628 -0
  109. package/dist/esm/run/flows/url/slides-text.js.map +1 -0
  110. package/dist/esm/run/flows/url/summary.js +358 -83
  111. package/dist/esm/run/flows/url/summary.js.map +1 -1
  112. package/dist/esm/run/help.js +94 -5
  113. package/dist/esm/run/help.js.map +1 -1
  114. package/dist/esm/run/logging.js +12 -4
  115. package/dist/esm/run/logging.js.map +1 -1
  116. package/dist/esm/run/media-cache-state.js +33 -0
  117. package/dist/esm/run/media-cache-state.js.map +1 -0
  118. package/dist/esm/run/progress.js +19 -1
  119. package/dist/esm/run/progress.js.map +1 -1
  120. package/dist/esm/run/run-context.js +19 -0
  121. package/dist/esm/run/run-context.js.map +1 -0
  122. package/dist/esm/run/run-output.js +1 -1
  123. package/dist/esm/run/run-output.js.map +1 -1
  124. package/dist/esm/run/run-settings.js +182 -0
  125. package/dist/esm/run/run-settings.js.map +1 -0
  126. package/dist/esm/run/runner.js +225 -32
  127. package/dist/esm/run/runner.js.map +1 -1
  128. package/dist/esm/run/slides-cli.js +225 -0
  129. package/dist/esm/run/slides-cli.js.map +1 -0
  130. package/dist/esm/run/slides-render.js +163 -0
  131. package/dist/esm/run/slides-render.js.map +1 -0
  132. package/dist/esm/run/stream-output.js +63 -0
  133. package/dist/esm/run/stream-output.js.map +1 -0
  134. package/dist/esm/run/streaming.js +16 -43
  135. package/dist/esm/run/streaming.js.map +1 -1
  136. package/dist/esm/run/summary-engine.js +59 -41
  137. package/dist/esm/run/summary-engine.js.map +1 -1
  138. package/dist/esm/run/transcriber-cli.js +148 -0
  139. package/dist/esm/run/transcriber-cli.js.map +1 -0
  140. package/dist/esm/shared/sse-events.js +26 -0
  141. package/dist/esm/shared/sse-events.js.map +1 -0
  142. package/dist/esm/shared/streaming-merge.js +44 -0
  143. package/dist/esm/shared/streaming-merge.js.map +1 -0
  144. package/dist/esm/slides/extract.js +1942 -0
  145. package/dist/esm/slides/extract.js.map +1 -0
  146. package/dist/esm/slides/index.js +4 -0
  147. package/dist/esm/slides/index.js.map +1 -0
  148. package/dist/esm/slides/settings.js +73 -0
  149. package/dist/esm/slides/settings.js.map +1 -0
  150. package/dist/esm/slides/store.js +111 -0
  151. package/dist/esm/slides/store.js.map +1 -0
  152. package/dist/esm/slides/types.js +2 -0
  153. package/dist/esm/slides/types.js.map +1 -0
  154. package/dist/esm/tty/osc-progress.js +21 -1
  155. package/dist/esm/tty/osc-progress.js.map +1 -1
  156. package/dist/esm/tty/progress/fetch-html.js +8 -4
  157. package/dist/esm/tty/progress/fetch-html.js.map +1 -1
  158. package/dist/esm/tty/progress/transcript.js +82 -31
  159. package/dist/esm/tty/progress/transcript.js.map +1 -1
  160. package/dist/esm/tty/spinner.js +2 -2
  161. package/dist/esm/tty/spinner.js.map +1 -1
  162. package/dist/esm/tty/theme.js +189 -0
  163. package/dist/esm/tty/theme.js.map +1 -0
  164. package/dist/esm/tty/website-progress.js +17 -13
  165. package/dist/esm/tty/website-progress.js.map +1 -1
  166. package/dist/esm/version.js +1 -1
  167. package/dist/esm/version.js.map +1 -1
  168. package/dist/types/cache.d.ts +14 -2
  169. package/dist/types/config.d.ts +34 -0
  170. package/dist/types/daemon/agent.d.ts +25 -0
  171. package/dist/types/daemon/chat.d.ts +27 -0
  172. package/dist/types/daemon/env-snapshot.d.ts +1 -1
  173. package/dist/types/daemon/flow-context.d.ts +24 -3
  174. package/dist/types/daemon/launchd.d.ts +4 -0
  175. package/dist/types/daemon/process-registry.d.ts +73 -0
  176. package/dist/types/daemon/schtasks.d.ts +4 -0
  177. package/dist/types/daemon/server.d.ts +7 -1
  178. package/dist/types/daemon/summarize.d.ts +47 -5
  179. package/dist/types/daemon/systemd.d.ts +4 -0
  180. package/dist/types/flags.d.ts +1 -0
  181. package/dist/types/llm/attachments.d.ts +6 -0
  182. package/dist/types/llm/errors.d.ts +1 -0
  183. package/dist/types/llm/generate-text.d.ts +29 -13
  184. package/dist/types/llm/prompt.d.ts +7 -2
  185. package/dist/types/llm/providers/anthropic.d.ts +30 -0
  186. package/dist/types/llm/providers/google.d.ts +29 -0
  187. package/dist/types/llm/providers/models.d.ts +27 -0
  188. package/dist/types/llm/providers/openai.d.ts +38 -0
  189. package/dist/types/llm/providers/shared.d.ts +14 -0
  190. package/dist/types/llm/providers/types.d.ts +6 -0
  191. package/dist/types/llm/types.d.ts +5 -0
  192. package/dist/types/llm/usage.d.ts +5 -0
  193. package/dist/types/logging/daemon.d.ts +26 -0
  194. package/dist/types/logging/ring-file.d.ts +10 -0
  195. package/dist/types/media-cache.d.ts +22 -0
  196. package/dist/types/model-auto.d.ts +1 -0
  197. package/dist/types/processes.d.ts +1 -0
  198. package/dist/types/run/attachments.d.ts +9 -6
  199. package/dist/types/run/bird.d.ts +7 -0
  200. package/dist/types/run/constants.d.ts +0 -2
  201. package/dist/types/run/finish-line.d.ts +59 -1
  202. package/dist/types/run/flows/asset/extract.d.ts +18 -0
  203. package/dist/types/run/flows/asset/input.d.ts +12 -2
  204. package/dist/types/run/flows/asset/media-policy.d.ts +2 -0
  205. package/dist/types/run/flows/asset/media.d.ts +21 -0
  206. package/dist/types/run/flows/asset/output.d.ts +42 -0
  207. package/dist/types/run/flows/asset/preprocess.d.ts +22 -2
  208. package/dist/types/run/flows/asset/summary.d.ts +6 -0
  209. package/dist/types/run/flows/url/extract.d.ts +2 -1
  210. package/dist/types/run/flows/url/slides-output.d.ts +66 -0
  211. package/dist/types/run/flows/url/slides-text.d.ts +87 -0
  212. package/dist/types/run/flows/url/summary.d.ts +11 -3
  213. package/dist/types/run/flows/url/types.d.ts +29 -2
  214. package/dist/types/run/help.d.ts +3 -0
  215. package/dist/types/run/logging.d.ts +3 -2
  216. package/dist/types/run/media-cache-state.d.ts +7 -0
  217. package/dist/types/run/progress.d.ts +2 -1
  218. package/dist/types/run/run-context.d.ts +44 -0
  219. package/dist/types/run/run-settings.d.ts +62 -0
  220. package/dist/types/run/slides-cli.d.ts +9 -0
  221. package/dist/types/run/slides-render.d.ts +30 -0
  222. package/dist/types/run/stream-output.d.ts +12 -0
  223. package/dist/types/run/streaming.d.ts +10 -4
  224. package/dist/types/run/summary-engine.d.ts +15 -3
  225. package/dist/types/run/summary-llm.d.ts +2 -2
  226. package/dist/types/run/transcriber-cli.d.ts +8 -0
  227. package/dist/types/shared/sse-events.d.ts +64 -0
  228. package/dist/types/shared/streaming-merge.d.ts +4 -0
  229. package/dist/types/slides/extract.d.ts +42 -0
  230. package/dist/types/slides/index.d.ts +5 -0
  231. package/dist/types/slides/settings.d.ts +20 -0
  232. package/dist/types/slides/store.d.ts +15 -0
  233. package/dist/types/slides/types.d.ts +40 -0
  234. package/dist/types/tty/osc-progress.d.ts +2 -2
  235. package/dist/types/tty/progress/fetch-html.d.ts +3 -1
  236. package/dist/types/tty/progress/transcript.d.ts +3 -1
  237. package/dist/types/tty/spinner.d.ts +3 -1
  238. package/dist/types/tty/theme.d.ts +44 -0
  239. package/dist/types/tty/website-progress.d.ts +3 -1
  240. package/dist/types/version.d.ts +1 -1
  241. package/docs/README.md +13 -8
  242. package/docs/_config.yml +26 -0
  243. package/docs/_layouts/default.html +60 -0
  244. package/docs/agent.md +333 -0
  245. package/docs/assets/site.css +748 -0
  246. package/docs/assets/site.js +72 -0
  247. package/docs/assets/summarize-cli.png +0 -0
  248. package/docs/assets/summarize-extension.png +0 -0
  249. package/docs/assets/youtube-slides.png +0 -0
  250. package/docs/cache.md +29 -3
  251. package/docs/chrome-extension.md +85 -7
  252. package/docs/config.md +74 -2
  253. package/docs/extract-only.md +10 -2
  254. package/docs/index.html +205 -0
  255. package/docs/index.md +25 -0
  256. package/docs/language.md +1 -1
  257. package/docs/llm.md +17 -1
  258. package/docs/manual-tests.md +2 -0
  259. package/docs/media.md +37 -0
  260. package/docs/model-auto.md +2 -1
  261. package/docs/nvidia-onnx-transcription.md +55 -0
  262. package/docs/openai.md +5 -0
  263. package/docs/releasing.md +26 -0
  264. package/docs/site/assets/site.css +399 -228
  265. package/docs/site/assets/summarize-cli.png +0 -0
  266. package/docs/site/assets/summarize-extension.png +0 -0
  267. package/docs/site/docs/chrome-extension.html +89 -0
  268. package/docs/site/docs/config.html +1 -0
  269. package/docs/site/docs/extract-only.html +1 -0
  270. package/docs/site/docs/firecrawl.html +1 -0
  271. package/docs/site/docs/index.html +5 -0
  272. package/docs/site/docs/llm.html +1 -0
  273. package/docs/site/docs/openai.html +1 -0
  274. package/docs/site/docs/website.html +1 -0
  275. package/docs/site/docs/youtube.html +1 -0
  276. package/docs/site/index.html +148 -84
  277. package/docs/slides.md +74 -0
  278. package/docs/timestamps.md +103 -0
  279. package/docs/website.md +13 -0
  280. package/docs/youtube.md +16 -0
  281. package/package.json +22 -18
  282. package/dist/esm/daemon/request-settings.js +0 -91
  283. package/dist/esm/daemon/request-settings.js.map +0 -1
  284. package/dist/types/daemon/request-settings.d.ts +0 -27
@@ -1,265 +1,169 @@
1
- import { completeSimple, getModel, streamSimple } from '@mariozechner/pi-ai';
1
+ import { completeSimple, streamSimple } from '@mariozechner/pi-ai';
2
+ import { createUnsupportedFunctionalityError } from './errors.js';
2
3
  import { parseGatewayStyleModelId } from './model-id.js';
3
- function parseAnthropicErrorPayload(responseBody) {
4
- try {
5
- const parsed = JSON.parse(responseBody);
6
- if (parsed?.type !== 'error')
7
- return null;
8
- const error = parsed.error;
9
- if (!error || typeof error !== 'object')
10
- return null;
11
- const errorType = typeof error.type === 'string' ? error.type : null;
12
- const errorMessage = typeof error.message === 'string' ? error.message : null;
13
- if (!errorType || !errorMessage)
14
- return null;
15
- return { type: errorType, message: errorMessage };
4
+ import { userTextAndImageMessage } from './prompt.js';
5
+ import { completeAnthropicDocument, completeAnthropicText, normalizeAnthropicModelAccessError, } from './providers/anthropic.js';
6
+ import { completeGoogleDocument, completeGoogleText } from './providers/google.js';
7
+ import { resolveAnthropicModel, resolveGoogleModel, resolveOpenAiModel, resolveXaiModel, resolveZaiModel, } from './providers/models.js';
8
+ import { completeOpenAiDocument, completeOpenAiText, resolveOpenAiClientConfig, } from './providers/openai.js';
9
+ import { extractText } from './providers/shared.js';
10
+ import { normalizeTokenUsage } from './usage.js';
11
+ function promptToContext(prompt) {
12
+ const attachments = prompt.attachments ?? [];
13
+ if (attachments.some((attachment) => attachment.kind === 'document')) {
14
+ throw new Error('Internal error: document prompt cannot be converted to context.');
16
15
  }
17
- catch {
18
- return null;
16
+ if (attachments.length === 0) {
17
+ return {
18
+ systemPrompt: prompt.system,
19
+ messages: [{ role: 'user', content: prompt.userText, timestamp: Date.now() }],
20
+ };
19
21
  }
20
- }
21
- function normalizeAnthropicModelAccessError(error, modelId) {
22
- if (!error || typeof error !== 'object')
23
- return null;
24
- const maybe = error;
25
- const statusCode = typeof maybe.statusCode === 'number' ? maybe.statusCode : null;
26
- const responseBody = typeof maybe.responseBody === 'string' ? maybe.responseBody : null;
27
- const payload = responseBody ? parseAnthropicErrorPayload(responseBody) : null;
28
- const payloadType = payload?.type ?? null;
29
- const payloadMessage = payload?.message ?? null;
30
- const message = typeof maybe.message === 'string' ? maybe.message : '';
31
- const combinedMessage = (payloadMessage ?? message).trim();
32
- const hasModelMessage = /^model:\s*\S+/i.test(combinedMessage);
33
- const isAccessStatus = statusCode === 401 || statusCode === 403 || statusCode === 404;
34
- const isAccessType = payloadType === 'not_found_error' ||
35
- payloadType === 'permission_error' ||
36
- payloadType === 'authentication_error';
37
- if (!hasModelMessage && !isAccessStatus && !isAccessType)
38
- return null;
39
- const modelLabel = hasModelMessage ? combinedMessage.replace(/^model:\s*/i, '').trim() : modelId;
40
- const hint = `Anthropic API rejected model "${modelLabel}". Your ANTHROPIC_API_KEY likely lacks access to this model or it is unavailable for your account. Try another anthropic/... model or request access.`;
41
- return new Error(hint, { cause: error instanceof Error ? error : undefined });
42
- }
43
- function normalizeTokenUsage(raw) {
44
- if (!raw || typeof raw !== 'object')
45
- return null;
46
- const usage = raw;
47
- const promptTokens = typeof usage.input === 'number' && Number.isFinite(usage.input) ? usage.input : null;
48
- const completionTokens = typeof usage.output === 'number' && Number.isFinite(usage.output) ? usage.output : null;
49
- const totalTokens = typeof usage.totalTokens === 'number' && Number.isFinite(usage.totalTokens)
50
- ? usage.totalTokens
51
- : null;
52
- if (promptTokens === null && completionTokens === null && totalTokens === null)
53
- return null;
54
- return { promptTokens, completionTokens, totalTokens };
55
- }
56
- function resolveBaseUrlOverride(raw) {
57
- const trimmed = typeof raw === 'string' ? raw.trim() : '';
58
- return trimmed.length > 0 ? trimmed : null;
59
- }
60
- function resolveOpenAiClientConfig({ apiKeys, forceOpenRouter, openaiBaseUrlOverride, forceChatCompletions, }) {
61
- const baseUrlRaw = openaiBaseUrlOverride ??
62
- (typeof process !== 'undefined' ? process.env.OPENAI_BASE_URL : undefined);
63
- const baseUrl = typeof baseUrlRaw === 'string' && baseUrlRaw.trim().length > 0 ? baseUrlRaw.trim() : null;
64
- const isOpenRouterViaBaseUrl = baseUrl ? /openrouter\.ai/i.test(baseUrl) : false;
65
- const hasOpenRouterKey = apiKeys.openrouterApiKey != null;
66
- const hasOpenAiKey = apiKeys.openaiApiKey != null;
67
- const isOpenRouter = Boolean(forceOpenRouter) ||
68
- isOpenRouterViaBaseUrl ||
69
- (hasOpenRouterKey && !baseUrl && !hasOpenAiKey);
70
- const apiKey = isOpenRouter
71
- ? (apiKeys.openrouterApiKey ?? apiKeys.openaiApiKey)
72
- : apiKeys.openaiApiKey;
73
- if (!apiKey) {
74
- throw new Error(isOpenRouter
75
- ? 'Missing OPENROUTER_API_KEY (or OPENAI_API_KEY) for OpenRouter'
76
- : 'Missing OPENAI_API_KEY for openai/... model');
22
+ if (attachments.length !== 1 || attachments[0]?.kind !== 'image') {
23
+ throw new Error('Internal error: only single image attachments are supported for prompts.');
77
24
  }
78
- const baseURL = forceOpenRouter
79
- ? 'https://openrouter.ai/api/v1'
80
- : (baseUrl ?? (isOpenRouter ? 'https://openrouter.ai/api/v1' : undefined));
81
- const isCustomBaseURL = (() => {
82
- if (!baseURL)
83
- return false;
84
- try {
85
- const url = new URL(baseURL);
86
- return url.host !== 'api.openai.com' && url.host !== 'openrouter.ai';
87
- }
88
- catch {
89
- return false;
90
- }
91
- })();
92
- const useChatCompletions = Boolean(forceChatCompletions) || isOpenRouter || isCustomBaseURL;
93
- return {
94
- apiKey,
95
- baseURL: baseURL ?? undefined,
96
- useChatCompletions,
97
- isOpenRouter,
98
- };
25
+ const attachment = attachments[0];
26
+ const messages = [
27
+ userTextAndImageMessage({
28
+ text: prompt.userText,
29
+ imageBytes: attachment.bytes,
30
+ mimeType: attachment.mediaType,
31
+ }),
32
+ ];
33
+ return { systemPrompt: prompt.system, messages };
99
34
  }
100
- function promptToContext({ system, prompt }) {
101
- const messages = typeof prompt === 'string'
102
- ? [{ role: 'user', content: prompt, timestamp: Date.now() }]
103
- : prompt.map((msg) => typeof msg.timestamp === 'number'
104
- ? msg
105
- : { ...msg, timestamp: Date.now() });
106
- return { systemPrompt: system, messages };
35
+ function isRetryableTimeoutError(error) {
36
+ if (!error)
37
+ return false;
38
+ const message = typeof error === 'string'
39
+ ? error
40
+ : error instanceof Error
41
+ ? error.message
42
+ : typeof error.message === 'string'
43
+ ? String(error.message)
44
+ : '';
45
+ return /timed out/i.test(message) || /empty summary/i.test(message);
107
46
  }
108
- function extractText(message) {
109
- const text = message.content
110
- .filter((c) => c.type === 'text')
111
- .map((c) => c.text)
112
- .join('');
113
- return text.trim();
47
+ function computeRetryDelayMs(attempt) {
48
+ const base = 500;
49
+ const jitter = Math.floor(Math.random() * 200);
50
+ return Math.min(2000, base * (attempt + 1) + jitter);
114
51
  }
115
- function wantsImages(context) {
116
- for (const msg of context.messages) {
117
- if (msg.role === 'user' || msg.role === 'toolResult') {
118
- if (Array.isArray(msg.content) && msg.content.some((c) => c.type === 'image'))
119
- return true;
120
- }
121
- }
122
- return false;
52
+ function sleep(ms) {
53
+ return new Promise((resolve) => setTimeout(resolve, ms));
123
54
  }
124
- function tryGetModel(provider, modelId) {
125
- try {
126
- return getModel(provider, modelId);
127
- }
128
- catch {
129
- return null;
130
- }
55
+ function isOpenaiGpt5Model(parsed) {
56
+ return parsed.provider === 'openai' && /^gpt-5([-.].+)?$/i.test(parsed.model);
131
57
  }
132
- function createSyntheticModel({ provider, modelId, api, baseUrl, allowImages, headers, }) {
133
- return {
134
- id: modelId,
135
- name: `${provider}/${modelId}`,
136
- api,
137
- provider,
138
- baseUrl,
139
- reasoning: false,
140
- input: allowImages ? ['text', 'image'] : ['text'],
141
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
142
- contextWindow: 128_000,
143
- maxTokens: 16_384,
144
- ...(headers ? { headers } : {}),
145
- };
58
+ function resolveEffectiveTemperature({ parsed, temperature, }) {
59
+ if (typeof temperature !== 'number')
60
+ return undefined;
61
+ if (isOpenaiGpt5Model(parsed))
62
+ return undefined;
63
+ return temperature;
146
64
  }
147
- function resolveModelForCall({ modelId, parsedProvider, openaiConfig, context, openaiBaseUrlOverride, anthropicBaseUrlOverride, googleBaseUrlOverride, xaiBaseUrlOverride, }) {
148
- const allowImages = wantsImages(context);
149
- if (parsedProvider === 'openai') {
150
- const base = tryGetModel('openai', modelId);
151
- const api = openaiConfig?.useChatCompletions ? 'openai-completions' : 'openai-responses';
152
- const baseUrl = openaiConfig?.baseURL ?? base?.baseUrl ?? 'https://api.openai.com/v1';
153
- const headers = openaiConfig?.isOpenRouter
154
- ? {
155
- ...(base?.headers ?? {}),
156
- 'HTTP-Referer': 'https://github.com/steipete/summarize',
157
- 'X-Title': 'summarize',
65
+ export async function generateTextWithModelId({ modelId, apiKeys, prompt, temperature, maxOutputTokens, timeoutMs, fetchImpl, forceOpenRouter, openaiBaseUrlOverride, anthropicBaseUrlOverride, googleBaseUrlOverride, xaiBaseUrlOverride, forceChatCompletions, retries = 0, onRetry, }) {
66
+ const parsed = parseGatewayStyleModelId(modelId);
67
+ const effectiveTemperature = resolveEffectiveTemperature({ parsed, temperature });
68
+ const attachments = prompt.attachments ?? [];
69
+ const documentAttachment = attachments.find((attachment) => attachment.kind === 'document') ?? null;
70
+ if (documentAttachment) {
71
+ if (attachments.length !== 1) {
72
+ throw new Error('Internal error: document attachments cannot be combined with other inputs.');
73
+ }
74
+ if (parsed.provider === 'anthropic') {
75
+ const apiKey = apiKeys.anthropicApiKey;
76
+ if (!apiKey)
77
+ throw new Error('Missing ANTHROPIC_API_KEY for anthropic/... model');
78
+ try {
79
+ const result = await completeAnthropicDocument({
80
+ modelId: parsed.model,
81
+ apiKey,
82
+ promptText: prompt.userText,
83
+ document: documentAttachment,
84
+ system: prompt.system,
85
+ maxOutputTokens,
86
+ timeoutMs,
87
+ fetchImpl,
88
+ anthropicBaseUrlOverride,
89
+ });
90
+ return {
91
+ text: result.text,
92
+ canonicalModelId: parsed.canonical,
93
+ provider: parsed.provider,
94
+ usage: result.usage,
95
+ };
158
96
  }
159
- : base?.headers;
160
- return {
161
- ...(base ?? createSyntheticModel({ provider: 'openai', modelId, api, baseUrl, allowImages })),
162
- api,
163
- baseUrl,
164
- ...(headers ? { headers } : {}),
165
- };
166
- }
167
- if (parsedProvider === 'zai') {
168
- const base = tryGetModel('zai', modelId);
169
- const api = 'openai-completions';
170
- const baseUrl = openaiBaseUrlOverride ??
171
- base?.baseUrl ??
172
- openaiConfig?.baseURL ??
173
- 'https://api.z.ai/api/paas/v4';
174
- return {
175
- ...(base ?? createSyntheticModel({ provider: 'zai', modelId, api, baseUrl, allowImages })),
176
- api,
177
- baseUrl,
178
- input: allowImages ? ['text', 'image'] : ['text'],
179
- };
180
- }
181
- if (parsedProvider === 'xai') {
182
- const base = tryGetModel('xai', modelId);
183
- const override = resolveBaseUrlOverride(xaiBaseUrlOverride);
184
- if (override) {
97
+ catch (error) {
98
+ const normalized = normalizeAnthropicModelAccessError(error, parsed.model);
99
+ if (normalized)
100
+ throw normalized;
101
+ throw error;
102
+ }
103
+ }
104
+ if (parsed.provider === 'openai') {
105
+ const openaiConfig = resolveOpenAiClientConfig({
106
+ apiKeys: {
107
+ openaiApiKey: apiKeys.openaiApiKey,
108
+ openrouterApiKey: apiKeys.openrouterApiKey,
109
+ },
110
+ forceOpenRouter,
111
+ openaiBaseUrlOverride,
112
+ forceChatCompletions,
113
+ });
114
+ const result = await completeOpenAiDocument({
115
+ modelId: parsed.model,
116
+ openaiConfig,
117
+ promptText: prompt.userText,
118
+ document: documentAttachment,
119
+ maxOutputTokens,
120
+ temperature: effectiveTemperature,
121
+ timeoutMs,
122
+ fetchImpl,
123
+ });
185
124
  return {
186
- ...(base ??
187
- createSyntheticModel({
188
- provider: 'xai',
189
- modelId,
190
- api: 'openai-completions',
191
- baseUrl: override,
192
- allowImages,
193
- })),
194
- baseUrl: override,
125
+ text: result.text,
126
+ canonicalModelId: parsed.canonical,
127
+ provider: parsed.provider,
128
+ usage: result.usage,
195
129
  };
196
130
  }
197
- return (base ??
198
- createSyntheticModel({
199
- provider: 'xai',
200
- modelId,
201
- api: 'openai-completions',
202
- baseUrl: 'https://api.x.ai/v1',
203
- allowImages,
204
- }));
205
- }
206
- if (parsedProvider === 'google') {
207
- const base = tryGetModel('google', modelId);
208
- const override = resolveBaseUrlOverride(googleBaseUrlOverride);
209
- if (override) {
131
+ if (parsed.provider === 'google') {
132
+ const apiKey = apiKeys.googleApiKey;
133
+ if (!apiKey)
134
+ throw new Error('Missing GEMINI_API_KEY (or GOOGLE_GENERATIVE_AI_API_KEY / GOOGLE_API_KEY) for google/... model');
135
+ const result = await completeGoogleDocument({
136
+ modelId: parsed.model,
137
+ apiKey,
138
+ promptText: prompt.userText,
139
+ document: documentAttachment,
140
+ maxOutputTokens,
141
+ temperature: effectiveTemperature,
142
+ timeoutMs,
143
+ fetchImpl,
144
+ googleBaseUrlOverride,
145
+ });
210
146
  return {
211
- ...(base ??
212
- createSyntheticModel({
213
- provider: 'google',
214
- modelId,
215
- api: 'google-generative-ai',
216
- baseUrl: override,
217
- allowImages,
218
- })),
219
- baseUrl: override,
147
+ text: result.text,
148
+ canonicalModelId: parsed.canonical,
149
+ provider: parsed.provider,
150
+ usage: result.usage,
220
151
  };
221
152
  }
222
- return (base ??
223
- createSyntheticModel({
224
- provider: 'google',
225
- modelId,
226
- api: 'google-generative-ai',
227
- baseUrl: 'https://generativelanguage.googleapis.com/v1beta',
228
- allowImages,
229
- }));
153
+ throw createUnsupportedFunctionalityError(`document attachments are not supported for ${parsed.provider}/... models`);
230
154
  }
231
- const base = tryGetModel('anthropic', modelId);
232
- const override = resolveBaseUrlOverride(anthropicBaseUrlOverride);
233
- if (override) {
234
- return {
235
- ...(base ??
236
- createSyntheticModel({
237
- provider: 'anthropic',
238
- modelId,
239
- api: 'anthropic-messages',
240
- baseUrl: override,
241
- allowImages,
242
- })),
243
- baseUrl: override,
244
- };
245
- }
246
- return (base ??
247
- createSyntheticModel({
248
- provider: 'anthropic',
249
- modelId,
250
- api: 'anthropic-messages',
251
- baseUrl: 'https://api.anthropic.com',
252
- allowImages,
253
- }));
254
- }
255
- export async function generateTextWithModelId({ modelId, apiKeys, system, prompt, temperature, maxOutputTokens, timeoutMs, fetchImpl: _fetchImpl, forceOpenRouter, openaiBaseUrlOverride, anthropicBaseUrlOverride, googleBaseUrlOverride, xaiBaseUrlOverride, forceChatCompletions, retries = 0, onRetry, }) {
256
- void _fetchImpl;
257
- const parsed = parseGatewayStyleModelId(modelId);
258
- const context = promptToContext({ system, prompt });
259
- const isOpenaiGpt5 = parsed.provider === 'openai' && /^gpt-5([-.].+)?$/i.test(parsed.model);
260
- const effectiveTemperature = typeof temperature === 'number' && !(isOpenaiGpt5 && temperature === 0)
261
- ? temperature
262
- : undefined;
155
+ const context = promptToContext(prompt);
156
+ const openaiConfig = parsed.provider === 'openai'
157
+ ? resolveOpenAiClientConfig({
158
+ apiKeys: {
159
+ openaiApiKey: apiKeys.openaiApiKey,
160
+ openrouterApiKey: apiKeys.openrouterApiKey,
161
+ },
162
+ forceOpenRouter,
163
+ openaiBaseUrlOverride,
164
+ forceChatCompletions,
165
+ })
166
+ : null;
263
167
  const maxRetries = Math.max(0, retries);
264
168
  let attempt = 0;
265
169
  while (attempt <= maxRetries) {
@@ -270,10 +174,8 @@ export async function generateTextWithModelId({ modelId, apiKeys, system, prompt
270
174
  const apiKey = apiKeys.xaiApiKey;
271
175
  if (!apiKey)
272
176
  throw new Error('Missing XAI_API_KEY for xai/... model');
273
- const model = resolveModelForCall({
177
+ const model = resolveXaiModel({
274
178
  modelId: parsed.model,
275
- parsedProvider: parsed.provider,
276
- openaiConfig: null,
277
179
  context,
278
180
  xaiBaseUrlOverride,
279
181
  });
@@ -299,76 +201,48 @@ export async function generateTextWithModelId({ modelId, apiKeys, system, prompt
299
201
  const apiKey = apiKeys.googleApiKey;
300
202
  if (!apiKey)
301
203
  throw new Error('Missing GEMINI_API_KEY (or GOOGLE_GENERATIVE_AI_API_KEY / GOOGLE_API_KEY) for google/... model');
302
- const model = resolveModelForCall({
204
+ const result = await completeGoogleText({
303
205
  modelId: parsed.model,
304
- parsedProvider: parsed.provider,
305
- openaiConfig: null,
306
- context,
307
- googleBaseUrlOverride,
308
- });
309
- const result = await completeSimple(model, context, {
310
- ...(typeof effectiveTemperature === 'number'
311
- ? { temperature: effectiveTemperature }
312
- : {}),
313
- ...(typeof maxOutputTokens === 'number' ? { maxTokens: maxOutputTokens } : {}),
314
206
  apiKey,
207
+ context,
208
+ temperature: effectiveTemperature,
209
+ maxOutputTokens,
315
210
  signal: controller.signal,
211
+ googleBaseUrlOverride,
316
212
  });
317
- const text = extractText(result);
318
- if (!text)
319
- throw new Error(`LLM returned an empty summary (model ${parsed.canonical}).`);
320
213
  return {
321
- text,
214
+ text: result.text,
322
215
  canonicalModelId: parsed.canonical,
323
216
  provider: parsed.provider,
324
- usage: normalizeTokenUsage(result.usage),
217
+ usage: result.usage,
325
218
  };
326
219
  }
327
220
  if (parsed.provider === 'anthropic') {
328
221
  const apiKey = apiKeys.anthropicApiKey;
329
222
  if (!apiKey)
330
223
  throw new Error('Missing ANTHROPIC_API_KEY for anthropic/... model');
331
- const model = resolveModelForCall({
224
+ const result = await completeAnthropicText({
332
225
  modelId: parsed.model,
333
- parsedProvider: parsed.provider,
334
- openaiConfig: null,
335
- context,
336
- anthropicBaseUrlOverride,
337
- });
338
- const result = await completeSimple(model, context, {
339
- ...(typeof effectiveTemperature === 'number'
340
- ? { temperature: effectiveTemperature }
341
- : {}),
342
- ...(typeof maxOutputTokens === 'number' ? { maxTokens: maxOutputTokens } : {}),
343
226
  apiKey,
227
+ context,
228
+ temperature: effectiveTemperature,
229
+ maxOutputTokens,
344
230
  signal: controller.signal,
231
+ anthropicBaseUrlOverride,
345
232
  });
346
- const text = extractText(result);
347
- if (!text)
348
- throw new Error(`LLM returned an empty summary (model ${parsed.canonical}).`);
349
233
  return {
350
- text,
234
+ text: result.text,
351
235
  canonicalModelId: parsed.canonical,
352
236
  provider: parsed.provider,
353
- usage: normalizeTokenUsage(result.usage),
237
+ usage: result.usage,
354
238
  };
355
239
  }
356
- const openaiConfig = parsed.provider === 'openai'
357
- ? resolveOpenAiClientConfig({
358
- apiKeys,
359
- forceOpenRouter,
360
- openaiBaseUrlOverride,
361
- forceChatCompletions,
362
- })
363
- : null;
364
240
  if (parsed.provider === 'zai') {
365
241
  const apiKey = apiKeys.openaiApiKey;
366
242
  if (!apiKey)
367
243
  throw new Error('Missing Z_AI_API_KEY for zai/... model');
368
- const model = resolveModelForCall({
244
+ const model = resolveZaiModel({
369
245
  modelId: parsed.model,
370
- parsedProvider: parsed.provider,
371
- openaiConfig: null,
372
246
  context,
373
247
  openaiBaseUrlOverride,
374
248
  });
@@ -390,30 +264,22 @@ export async function generateTextWithModelId({ modelId, apiKeys, system, prompt
390
264
  usage: normalizeTokenUsage(result.usage),
391
265
  };
392
266
  }
393
- const model = resolveModelForCall({
267
+ if (!openaiConfig) {
268
+ throw new Error('Missing OPENAI_API_KEY for openai/... model');
269
+ }
270
+ const result = await completeOpenAiText({
394
271
  modelId: parsed.model,
395
- parsedProvider: parsed.provider,
396
272
  openaiConfig,
397
273
  context,
398
- openaiBaseUrlOverride,
399
- anthropicBaseUrlOverride,
400
- googleBaseUrlOverride,
401
- xaiBaseUrlOverride,
402
- });
403
- const result = await completeSimple(model, context, {
404
- ...(typeof effectiveTemperature === 'number' ? { temperature: effectiveTemperature } : {}),
405
- ...(typeof maxOutputTokens === 'number' ? { maxTokens: maxOutputTokens } : {}),
406
- apiKey: openaiConfig?.apiKey ?? apiKeys.openaiApiKey ?? undefined,
274
+ temperature: effectiveTemperature,
275
+ maxOutputTokens,
407
276
  signal: controller.signal,
408
277
  });
409
- const text = extractText(result);
410
- if (!text)
411
- throw new Error(`LLM returned an empty summary (model ${parsed.canonical}).`);
412
278
  return {
413
- text,
279
+ text: result.text,
414
280
  canonicalModelId: parsed.canonical,
415
281
  provider: parsed.provider,
416
- usage: normalizeTokenUsage(result.usage),
282
+ usage: result.usage,
417
283
  };
418
284
  }
419
285
  catch (error) {
@@ -440,30 +306,28 @@ export async function generateTextWithModelId({ modelId, apiKeys, system, prompt
440
306
  }
441
307
  throw new Error(`LLM request failed after ${maxRetries + 1} attempts.`);
442
308
  }
443
- function isRetryableTimeoutError(error) {
444
- if (!error)
445
- return false;
446
- const message = typeof error === 'string'
447
- ? error
448
- : error instanceof Error
449
- ? error.message
450
- : typeof error.message === 'string'
451
- ? String(error.message)
452
- : '';
453
- return /timed out/i.test(message) || /empty summary/i.test(message);
454
- }
455
- function computeRetryDelayMs(attempt) {
456
- const base = 500;
457
- const jitter = Math.floor(Math.random() * 200);
458
- return Math.min(2000, base * (attempt + 1) + jitter);
459
- }
460
- function sleep(ms) {
461
- return new Promise((resolve) => setTimeout(resolve, ms));
309
+ export async function streamTextWithModelId({ modelId, apiKeys, prompt, temperature, maxOutputTokens, timeoutMs, fetchImpl, forceOpenRouter, openaiBaseUrlOverride, anthropicBaseUrlOverride, googleBaseUrlOverride, xaiBaseUrlOverride, forceChatCompletions, }) {
310
+ const context = promptToContext(prompt);
311
+ return streamTextWithContext({
312
+ modelId,
313
+ apiKeys,
314
+ context,
315
+ temperature,
316
+ maxOutputTokens,
317
+ timeoutMs,
318
+ fetchImpl,
319
+ forceOpenRouter,
320
+ openaiBaseUrlOverride,
321
+ anthropicBaseUrlOverride,
322
+ googleBaseUrlOverride,
323
+ xaiBaseUrlOverride,
324
+ forceChatCompletions,
325
+ });
462
326
  }
463
- export async function streamTextWithModelId({ modelId, apiKeys, system, prompt, temperature, maxOutputTokens, timeoutMs, fetchImpl: _fetchImpl, forceOpenRouter, openaiBaseUrlOverride, anthropicBaseUrlOverride, googleBaseUrlOverride, xaiBaseUrlOverride, forceChatCompletions, }) {
464
- void _fetchImpl;
327
+ export async function streamTextWithContext({ modelId, apiKeys, context, temperature, maxOutputTokens, timeoutMs, fetchImpl, forceOpenRouter, openaiBaseUrlOverride, anthropicBaseUrlOverride, googleBaseUrlOverride, xaiBaseUrlOverride, forceChatCompletions, }) {
465
328
  const parsed = parseGatewayStyleModelId(modelId);
466
- const context = promptToContext({ system, prompt });
329
+ const effectiveTemperature = resolveEffectiveTemperature({ parsed, temperature });
330
+ void fetchImpl;
467
331
  const controller = new AbortController();
468
332
  let timeoutId = null;
469
333
  const startedAtMs = Date.now();
@@ -539,15 +403,13 @@ export async function streamTextWithModelId({ modelId, apiKeys, system, prompt,
539
403
  const apiKey = apiKeys.xaiApiKey;
540
404
  if (!apiKey)
541
405
  throw new Error('Missing XAI_API_KEY for xai/... model');
542
- const model = resolveModelForCall({
406
+ const model = resolveXaiModel({
543
407
  modelId: parsed.model,
544
- parsedProvider: parsed.provider,
545
- openaiConfig: null,
546
408
  context,
547
409
  xaiBaseUrlOverride,
548
410
  });
549
411
  const stream = streamSimple(model, context, {
550
- ...(typeof temperature === 'number' ? { temperature } : {}),
412
+ ...(typeof effectiveTemperature === 'number' ? { temperature: effectiveTemperature } : {}),
551
413
  ...(typeof maxOutputTokens === 'number' ? { maxTokens: maxOutputTokens } : {}),
552
414
  apiKey,
553
415
  signal: controller.signal,
@@ -579,15 +441,13 @@ export async function streamTextWithModelId({ modelId, apiKeys, system, prompt,
579
441
  const apiKey = apiKeys.googleApiKey;
580
442
  if (!apiKey)
581
443
  throw new Error('Missing GEMINI_API_KEY (or GOOGLE_GENERATIVE_AI_API_KEY / GOOGLE_API_KEY) for google/... model');
582
- const model = resolveModelForCall({
444
+ const model = resolveGoogleModel({
583
445
  modelId: parsed.model,
584
- parsedProvider: parsed.provider,
585
- openaiConfig: null,
586
446
  context,
587
447
  googleBaseUrlOverride,
588
448
  });
589
449
  const stream = streamSimple(model, context, {
590
- ...(typeof temperature === 'number' ? { temperature } : {}),
450
+ ...(typeof effectiveTemperature === 'number' ? { temperature: effectiveTemperature } : {}),
591
451
  ...(typeof maxOutputTokens === 'number' ? { maxTokens: maxOutputTokens } : {}),
592
452
  apiKey,
593
453
  signal: controller.signal,
@@ -619,15 +479,13 @@ export async function streamTextWithModelId({ modelId, apiKeys, system, prompt,
619
479
  const apiKey = apiKeys.anthropicApiKey;
620
480
  if (!apiKey)
621
481
  throw new Error('Missing ANTHROPIC_API_KEY for anthropic/... model');
622
- const model = resolveModelForCall({
482
+ const model = resolveAnthropicModel({
623
483
  modelId: parsed.model,
624
- parsedProvider: parsed.provider,
625
- openaiConfig: null,
626
484
  context,
627
485
  anthropicBaseUrlOverride,
628
486
  });
629
487
  const stream = streamSimple(model, context, {
630
- ...(typeof temperature === 'number' ? { temperature } : {}),
488
+ ...(typeof effectiveTemperature === 'number' ? { temperature: effectiveTemperature } : {}),
631
489
  ...(typeof maxOutputTokens === 'number' ? { maxTokens: maxOutputTokens } : {}),
632
490
  apiKey,
633
491
  signal: controller.signal,
@@ -660,15 +518,13 @@ export async function streamTextWithModelId({ modelId, apiKeys, system, prompt,
660
518
  const apiKey = apiKeys.openaiApiKey;
661
519
  if (!apiKey)
662
520
  throw new Error('Missing Z_AI_API_KEY for zai/... model');
663
- const model = resolveModelForCall({
521
+ const model = resolveZaiModel({
664
522
  modelId: parsed.model,
665
- parsedProvider: parsed.provider,
666
- openaiConfig: null,
667
523
  context,
668
524
  openaiBaseUrlOverride,
669
525
  });
670
526
  const stream = streamSimple(model, context, {
671
- ...(typeof temperature === 'number' ? { temperature } : {}),
527
+ ...(typeof effectiveTemperature === 'number' ? { temperature: effectiveTemperature } : {}),
672
528
  ...(typeof maxOutputTokens === 'number' ? { maxTokens: maxOutputTokens } : {}),
673
529
  apiKey,
674
530
  signal: controller.signal,
@@ -697,23 +553,17 @@ export async function streamTextWithModelId({ modelId, apiKeys, system, prompt,
697
553
  };
698
554
  }
699
555
  const openaiConfig = resolveOpenAiClientConfig({
700
- apiKeys,
556
+ apiKeys: {
557
+ openaiApiKey: apiKeys.openaiApiKey,
558
+ openrouterApiKey: apiKeys.openrouterApiKey,
559
+ },
701
560
  forceOpenRouter,
702
561
  openaiBaseUrlOverride,
703
562
  forceChatCompletions,
704
563
  });
705
- const model = resolveModelForCall({
706
- modelId: parsed.model,
707
- parsedProvider: parsed.provider,
708
- openaiConfig,
709
- context,
710
- openaiBaseUrlOverride,
711
- anthropicBaseUrlOverride,
712
- googleBaseUrlOverride,
713
- xaiBaseUrlOverride,
714
- });
564
+ const model = resolveOpenAiModel({ modelId: parsed.model, context, openaiConfig });
715
565
  const stream = streamSimple(model, context, {
716
- ...(typeof temperature === 'number' ? { temperature } : {}),
566
+ ...(typeof effectiveTemperature === 'number' ? { temperature: effectiveTemperature } : {}),
717
567
  ...(typeof maxOutputTokens === 'number' ? { maxTokens: maxOutputTokens } : {}),
718
568
  apiKey: openaiConfig.apiKey,
719
569
  signal: controller.signal,