@gajae-code/ai 0.1.1

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 (349) hide show
  1. package/CHANGELOG.md +2644 -0
  2. package/README.md +1181 -0
  3. package/dist/types/api-registry.d.ts +30 -0
  4. package/dist/types/auth-broker/client.d.ts +66 -0
  5. package/dist/types/auth-broker/index.d.ts +5 -0
  6. package/dist/types/auth-broker/refresher.d.ts +25 -0
  7. package/dist/types/auth-broker/remote-store.d.ts +96 -0
  8. package/dist/types/auth-broker/server.d.ts +32 -0
  9. package/dist/types/auth-broker/types.d.ts +105 -0
  10. package/dist/types/auth-broker/wire-schemas.d.ts +412 -0
  11. package/dist/types/auth-gateway/http.d.ts +39 -0
  12. package/dist/types/auth-gateway/index.d.ts +3 -0
  13. package/dist/types/auth-gateway/server.d.ts +17 -0
  14. package/dist/types/auth-gateway/types.d.ts +115 -0
  15. package/dist/types/auth-storage.d.ts +641 -0
  16. package/dist/types/cli.d.ts +2 -0
  17. package/dist/types/index.d.ts +49 -0
  18. package/dist/types/model-cache.d.ts +17 -0
  19. package/dist/types/model-manager.d.ts +62 -0
  20. package/dist/types/model-thinking.d.ts +71 -0
  21. package/dist/types/models.d.ts +12 -0
  22. package/dist/types/provider-details.d.ts +24 -0
  23. package/dist/types/provider-models/bundled-references.d.ts +4 -0
  24. package/dist/types/provider-models/descriptors.d.ts +48 -0
  25. package/dist/types/provider-models/google.d.ts +20 -0
  26. package/dist/types/provider-models/index.d.ts +5 -0
  27. package/dist/types/provider-models/ollama.d.ts +7 -0
  28. package/dist/types/provider-models/openai-compat.d.ts +237 -0
  29. package/dist/types/provider-models/special.d.ts +16 -0
  30. package/dist/types/providers/amazon-bedrock.d.ts +36 -0
  31. package/dist/types/providers/anthropic-messages-server-schema.d.ts +450 -0
  32. package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
  33. package/dist/types/providers/anthropic.d.ts +188 -0
  34. package/dist/types/providers/aws-credentials.d.ts +43 -0
  35. package/dist/types/providers/aws-eventstream.d.ts +38 -0
  36. package/dist/types/providers/aws-sigv4.d.ts +55 -0
  37. package/dist/types/providers/azure-openai-responses.d.ts +15 -0
  38. package/dist/types/providers/cursor/gen/agent_pb.d.ts +13022 -0
  39. package/dist/types/providers/cursor.d.ts +42 -0
  40. package/dist/types/providers/error-message.d.ts +27 -0
  41. package/dist/types/providers/github-copilot-headers.d.ts +40 -0
  42. package/dist/types/providers/gitlab-duo.d.ts +27 -0
  43. package/dist/types/providers/google-auth.d.ts +24 -0
  44. package/dist/types/providers/google-gemini-cli.d.ts +72 -0
  45. package/dist/types/providers/google-gemini-headers.d.ts +18 -0
  46. package/dist/types/providers/google-shared.d.ts +163 -0
  47. package/dist/types/providers/google-types.d.ts +138 -0
  48. package/dist/types/providers/google-vertex.d.ts +7 -0
  49. package/dist/types/providers/google.d.ts +4 -0
  50. package/dist/types/providers/grammar.d.ts +1 -0
  51. package/dist/types/providers/kimi.d.ts +27 -0
  52. package/dist/types/providers/mock.d.ts +175 -0
  53. package/dist/types/providers/ollama.d.ts +6 -0
  54. package/dist/types/providers/openai-anthropic-shim.d.ts +31 -0
  55. package/dist/types/providers/openai-chat-server-schema.d.ts +814 -0
  56. package/dist/types/providers/openai-chat-server.d.ts +16 -0
  57. package/dist/types/providers/openai-codex/constants.d.ts +26 -0
  58. package/dist/types/providers/openai-codex/request-transformer.d.ts +49 -0
  59. package/dist/types/providers/openai-codex/response-handler.d.ts +17 -0
  60. package/dist/types/providers/openai-codex-responses.d.ts +67 -0
  61. package/dist/types/providers/openai-completions-compat.d.ts +25 -0
  62. package/dist/types/providers/openai-completions.d.ts +33 -0
  63. package/dist/types/providers/openai-responses-server-schema.d.ts +392 -0
  64. package/dist/types/providers/openai-responses-server.d.ts +17 -0
  65. package/dist/types/providers/openai-responses-shared.d.ts +89 -0
  66. package/dist/types/providers/openai-responses.d.ts +32 -0
  67. package/dist/types/providers/pi-native-client.d.ts +13 -0
  68. package/dist/types/providers/pi-native-server.d.ts +68 -0
  69. package/dist/types/providers/register-builtins.d.ts +31 -0
  70. package/dist/types/providers/synthetic.d.ts +26 -0
  71. package/dist/types/providers/transform-messages.d.ts +12 -0
  72. package/dist/types/providers/vision-guard.d.ts +8 -0
  73. package/dist/types/rate-limit-utils.d.ts +19 -0
  74. package/dist/types/stream.d.ts +24 -0
  75. package/dist/types/types.d.ts +746 -0
  76. package/dist/types/usage/claude.d.ts +3 -0
  77. package/dist/types/usage/gemini.d.ts +2 -0
  78. package/dist/types/usage/github-copilot.d.ts +7 -0
  79. package/dist/types/usage/google-antigravity.d.ts +2 -0
  80. package/dist/types/usage/kimi.d.ts +2 -0
  81. package/dist/types/usage/minimax-code.d.ts +2 -0
  82. package/dist/types/usage/openai-codex.d.ts +3 -0
  83. package/dist/types/usage/shared.d.ts +1 -0
  84. package/dist/types/usage/zai.d.ts +2 -0
  85. package/dist/types/usage.d.ts +258 -0
  86. package/dist/types/utils/abort.d.ts +19 -0
  87. package/dist/types/utils/anthropic-auth.d.ts +31 -0
  88. package/dist/types/utils/discovery/antigravity.d.ts +61 -0
  89. package/dist/types/utils/discovery/codex.d.ts +38 -0
  90. package/dist/types/utils/discovery/cursor.d.ts +23 -0
  91. package/dist/types/utils/discovery/gemini.d.ts +25 -0
  92. package/dist/types/utils/discovery/index.d.ts +4 -0
  93. package/dist/types/utils/discovery/openai-compatible.d.ts +72 -0
  94. package/dist/types/utils/event-stream.d.ts +28 -0
  95. package/dist/types/utils/fireworks-model-id.d.ts +10 -0
  96. package/dist/types/utils/foundry.d.ts +1 -0
  97. package/dist/types/utils/h2-fetch.d.ts +22 -0
  98. package/dist/types/utils/http-inspector.d.ts +31 -0
  99. package/dist/types/utils/idle-iterator.d.ts +67 -0
  100. package/dist/types/utils/json-parse.d.ts +10 -0
  101. package/dist/types/utils/oauth/alibaba-coding-plan.d.ts +18 -0
  102. package/dist/types/utils/oauth/anthropic.d.ts +22 -0
  103. package/dist/types/utils/oauth/api-key-login.d.ts +35 -0
  104. package/dist/types/utils/oauth/api-key-validation.d.ts +27 -0
  105. package/dist/types/utils/oauth/callback-server.d.ts +57 -0
  106. package/dist/types/utils/oauth/cerebras.d.ts +1 -0
  107. package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +18 -0
  108. package/dist/types/utils/oauth/cursor.d.ts +15 -0
  109. package/dist/types/utils/oauth/deepseek.d.ts +10 -0
  110. package/dist/types/utils/oauth/firepass.d.ts +1 -0
  111. package/dist/types/utils/oauth/fireworks.d.ts +1 -0
  112. package/dist/types/utils/oauth/github-copilot.d.ts +38 -0
  113. package/dist/types/utils/oauth/gitlab-duo.d.ts +3 -0
  114. package/dist/types/utils/oauth/google-antigravity.d.ts +11 -0
  115. package/dist/types/utils/oauth/google-gemini-cli.d.ts +10 -0
  116. package/dist/types/utils/oauth/google-oauth-shared.d.ts +28 -0
  117. package/dist/types/utils/oauth/huggingface.d.ts +19 -0
  118. package/dist/types/utils/oauth/index.d.ts +38 -0
  119. package/dist/types/utils/oauth/kagi.d.ts +17 -0
  120. package/dist/types/utils/oauth/kilo.d.ts +5 -0
  121. package/dist/types/utils/oauth/kimi.d.ts +21 -0
  122. package/dist/types/utils/oauth/litellm.d.ts +18 -0
  123. package/dist/types/utils/oauth/lm-studio.d.ts +17 -0
  124. package/dist/types/utils/oauth/minimax-code.d.ts +28 -0
  125. package/dist/types/utils/oauth/moonshot.d.ts +1 -0
  126. package/dist/types/utils/oauth/nanogpt.d.ts +1 -0
  127. package/dist/types/utils/oauth/nvidia.d.ts +18 -0
  128. package/dist/types/utils/oauth/ollama-cloud.d.ts +2 -0
  129. package/dist/types/utils/oauth/ollama.d.ts +18 -0
  130. package/dist/types/utils/oauth/openai-codex.d.ts +21 -0
  131. package/dist/types/utils/oauth/opencode.d.ts +18 -0
  132. package/dist/types/utils/oauth/parallel.d.ts +17 -0
  133. package/dist/types/utils/oauth/perplexity.d.ts +9 -0
  134. package/dist/types/utils/oauth/pkce.d.ts +8 -0
  135. package/dist/types/utils/oauth/qianfan.d.ts +17 -0
  136. package/dist/types/utils/oauth/qwen-portal.d.ts +19 -0
  137. package/dist/types/utils/oauth/synthetic.d.ts +1 -0
  138. package/dist/types/utils/oauth/tavily.d.ts +17 -0
  139. package/dist/types/utils/oauth/together.d.ts +1 -0
  140. package/dist/types/utils/oauth/types.d.ts +44 -0
  141. package/dist/types/utils/oauth/venice.d.ts +18 -0
  142. package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +18 -0
  143. package/dist/types/utils/oauth/vllm.d.ts +16 -0
  144. package/dist/types/utils/oauth/xiaomi.d.ts +19 -0
  145. package/dist/types/utils/oauth/zai.d.ts +18 -0
  146. package/dist/types/utils/oauth/zenmux.d.ts +1 -0
  147. package/dist/types/utils/overflow.d.ts +54 -0
  148. package/dist/types/utils/parse-bind.d.ts +23 -0
  149. package/dist/types/utils/provider-response.d.ts +3 -0
  150. package/dist/types/utils/retry-after.d.ts +3 -0
  151. package/dist/types/utils/retry.d.ts +26 -0
  152. package/dist/types/utils/schema/adapt.d.ts +24 -0
  153. package/dist/types/utils/schema/compatibility.d.ts +30 -0
  154. package/dist/types/utils/schema/dereference.d.ts +11 -0
  155. package/dist/types/utils/schema/draft.d.ts +10 -0
  156. package/dist/types/utils/schema/equality.d.ts +4 -0
  157. package/dist/types/utils/schema/fields.d.ts +49 -0
  158. package/dist/types/utils/schema/index.d.ts +13 -0
  159. package/dist/types/utils/schema/json-schema-validator.d.ts +12 -0
  160. package/dist/types/utils/schema/meta-validator.d.ts +2 -0
  161. package/dist/types/utils/schema/normalize.d.ts +93 -0
  162. package/dist/types/utils/schema/spill.d.ts +8 -0
  163. package/dist/types/utils/schema/stamps.d.ts +25 -0
  164. package/dist/types/utils/schema/types.d.ts +4 -0
  165. package/dist/types/utils/schema/wire.d.ts +54 -0
  166. package/dist/types/utils/schema/zod-decontaminate.d.ts +31 -0
  167. package/dist/types/utils/sse-debug.d.ts +10 -0
  168. package/dist/types/utils/tool-call-healing.d.ts +71 -0
  169. package/dist/types/utils/tool-choice.d.ts +50 -0
  170. package/dist/types/utils/validation.d.ts +17 -0
  171. package/dist/types/utils.d.ts +28 -0
  172. package/package.json +146 -0
  173. package/src/api-registry.ts +96 -0
  174. package/src/auth-broker/client.ts +358 -0
  175. package/src/auth-broker/index.ts +5 -0
  176. package/src/auth-broker/refresher.ts +127 -0
  177. package/src/auth-broker/remote-store.ts +623 -0
  178. package/src/auth-broker/server.ts +644 -0
  179. package/src/auth-broker/types.ts +127 -0
  180. package/src/auth-broker/wire-schemas.ts +200 -0
  181. package/src/auth-gateway/http.ts +194 -0
  182. package/src/auth-gateway/index.ts +3 -0
  183. package/src/auth-gateway/server.ts +717 -0
  184. package/src/auth-gateway/types.ts +134 -0
  185. package/src/auth-storage.ts +4104 -0
  186. package/src/cli.ts +262 -0
  187. package/src/index.ts +54 -0
  188. package/src/model-cache.ts +129 -0
  189. package/src/model-manager.ts +450 -0
  190. package/src/model-thinking.ts +691 -0
  191. package/src/models.json +73853 -0
  192. package/src/models.json.d.ts +9 -0
  193. package/src/models.ts +56 -0
  194. package/src/prompts/turn-aborted-guidance.md +4 -0
  195. package/src/provider-details.ts +90 -0
  196. package/src/provider-models/bundled-references.ts +38 -0
  197. package/src/provider-models/descriptors.ts +308 -0
  198. package/src/provider-models/google.ts +91 -0
  199. package/src/provider-models/index.ts +5 -0
  200. package/src/provider-models/ollama.ts +153 -0
  201. package/src/provider-models/openai-compat.ts +2275 -0
  202. package/src/provider-models/special.ts +67 -0
  203. package/src/providers/amazon-bedrock.ts +849 -0
  204. package/src/providers/anthropic-messages-server-schema.ts +229 -0
  205. package/src/providers/anthropic-messages-server.ts +677 -0
  206. package/src/providers/anthropic.ts +2696 -0
  207. package/src/providers/aws-credentials.ts +501 -0
  208. package/src/providers/aws-eventstream.ts +185 -0
  209. package/src/providers/aws-sigv4.ts +218 -0
  210. package/src/providers/azure-openai-responses.ts +337 -0
  211. package/src/providers/cursor/gen/agent_pb.ts +15274 -0
  212. package/src/providers/cursor/proto/agent.proto +3526 -0
  213. package/src/providers/cursor/proto/buf.gen.yaml +6 -0
  214. package/src/providers/cursor/proto/buf.yaml +17 -0
  215. package/src/providers/cursor.ts +2561 -0
  216. package/src/providers/error-message.ts +21 -0
  217. package/src/providers/github-copilot-headers.ts +140 -0
  218. package/src/providers/gitlab-duo.ts +372 -0
  219. package/src/providers/google-auth.ts +252 -0
  220. package/src/providers/google-gemini-cli.ts +795 -0
  221. package/src/providers/google-gemini-headers.ts +41 -0
  222. package/src/providers/google-shared.ts +902 -0
  223. package/src/providers/google-types.ts +167 -0
  224. package/src/providers/google-vertex.ts +88 -0
  225. package/src/providers/google.ts +41 -0
  226. package/src/providers/grammar.ts +70 -0
  227. package/src/providers/kimi.ts +52 -0
  228. package/src/providers/mock.ts +500 -0
  229. package/src/providers/ollama.ts +544 -0
  230. package/src/providers/openai-anthropic-shim.ts +138 -0
  231. package/src/providers/openai-chat-server-schema.ts +243 -0
  232. package/src/providers/openai-chat-server.ts +628 -0
  233. package/src/providers/openai-codex/constants.ts +43 -0
  234. package/src/providers/openai-codex/request-transformer.ts +161 -0
  235. package/src/providers/openai-codex/response-handler.ts +81 -0
  236. package/src/providers/openai-codex-responses.ts +2598 -0
  237. package/src/providers/openai-completions-compat.ts +279 -0
  238. package/src/providers/openai-completions.ts +1853 -0
  239. package/src/providers/openai-responses-server-schema.ts +290 -0
  240. package/src/providers/openai-responses-server.ts +1183 -0
  241. package/src/providers/openai-responses-shared.ts +800 -0
  242. package/src/providers/openai-responses.ts +621 -0
  243. package/src/providers/pi-native-client.ts +228 -0
  244. package/src/providers/pi-native-server.ts +210 -0
  245. package/src/providers/register-builtins.ts +412 -0
  246. package/src/providers/synthetic.ts +50 -0
  247. package/src/providers/transform-messages.ts +309 -0
  248. package/src/providers/vision-guard.ts +31 -0
  249. package/src/rate-limit-utils.ts +84 -0
  250. package/src/stream.ts +895 -0
  251. package/src/types.ts +884 -0
  252. package/src/usage/claude.ts +431 -0
  253. package/src/usage/gemini.ts +250 -0
  254. package/src/usage/github-copilot.ts +421 -0
  255. package/src/usage/google-antigravity.ts +201 -0
  256. package/src/usage/kimi.ts +271 -0
  257. package/src/usage/minimax-code.ts +31 -0
  258. package/src/usage/openai-codex.ts +503 -0
  259. package/src/usage/shared.ts +10 -0
  260. package/src/usage/zai.ts +247 -0
  261. package/src/usage.ts +183 -0
  262. package/src/utils/abort.ts +51 -0
  263. package/src/utils/anthropic-auth.ts +87 -0
  264. package/src/utils/discovery/antigravity.ts +261 -0
  265. package/src/utils/discovery/codex.ts +371 -0
  266. package/src/utils/discovery/cursor.ts +306 -0
  267. package/src/utils/discovery/gemini.ts +248 -0
  268. package/src/utils/discovery/index.ts +4 -0
  269. package/src/utils/discovery/openai-compatible.ts +224 -0
  270. package/src/utils/event-stream.ts +142 -0
  271. package/src/utils/fireworks-model-id.ts +30 -0
  272. package/src/utils/foundry.ts +8 -0
  273. package/src/utils/h2-fetch.ts +60 -0
  274. package/src/utils/http-inspector.ts +176 -0
  275. package/src/utils/idle-iterator.ts +250 -0
  276. package/src/utils/json-parse.ts +148 -0
  277. package/src/utils/oauth/alibaba-coding-plan.ts +59 -0
  278. package/src/utils/oauth/anthropic.ts +200 -0
  279. package/src/utils/oauth/api-key-login.ts +87 -0
  280. package/src/utils/oauth/api-key-validation.ts +92 -0
  281. package/src/utils/oauth/callback-server.ts +276 -0
  282. package/src/utils/oauth/cerebras.ts +16 -0
  283. package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
  284. package/src/utils/oauth/cursor.ts +157 -0
  285. package/src/utils/oauth/deepseek.ts +53 -0
  286. package/src/utils/oauth/firepass.ts +24 -0
  287. package/src/utils/oauth/fireworks.ts +15 -0
  288. package/src/utils/oauth/github-copilot.ts +362 -0
  289. package/src/utils/oauth/gitlab-duo.ts +123 -0
  290. package/src/utils/oauth/google-antigravity.ts +200 -0
  291. package/src/utils/oauth/google-gemini-cli.ts +256 -0
  292. package/src/utils/oauth/google-oauth-shared.ts +110 -0
  293. package/src/utils/oauth/huggingface.ts +62 -0
  294. package/src/utils/oauth/index.ts +444 -0
  295. package/src/utils/oauth/kagi.ts +47 -0
  296. package/src/utils/oauth/kilo.ts +87 -0
  297. package/src/utils/oauth/kimi.ts +254 -0
  298. package/src/utils/oauth/litellm.ts +47 -0
  299. package/src/utils/oauth/lm-studio.ts +38 -0
  300. package/src/utils/oauth/minimax-code.ts +78 -0
  301. package/src/utils/oauth/moonshot.ts +16 -0
  302. package/src/utils/oauth/nanogpt.ts +15 -0
  303. package/src/utils/oauth/nvidia.ts +70 -0
  304. package/src/utils/oauth/oauth.html +199 -0
  305. package/src/utils/oauth/ollama-cloud.ts +28 -0
  306. package/src/utils/oauth/ollama.ts +47 -0
  307. package/src/utils/oauth/openai-codex.ts +299 -0
  308. package/src/utils/oauth/opencode.ts +49 -0
  309. package/src/utils/oauth/parallel.ts +46 -0
  310. package/src/utils/oauth/perplexity.ts +206 -0
  311. package/src/utils/oauth/pkce.ts +18 -0
  312. package/src/utils/oauth/qianfan.ts +58 -0
  313. package/src/utils/oauth/qwen-portal.ts +60 -0
  314. package/src/utils/oauth/synthetic.ts +16 -0
  315. package/src/utils/oauth/tavily.ts +46 -0
  316. package/src/utils/oauth/together.ts +16 -0
  317. package/src/utils/oauth/types.ts +94 -0
  318. package/src/utils/oauth/venice.ts +59 -0
  319. package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
  320. package/src/utils/oauth/vllm.ts +40 -0
  321. package/src/utils/oauth/xiaomi.ts +137 -0
  322. package/src/utils/oauth/zai.ts +60 -0
  323. package/src/utils/oauth/zenmux.ts +15 -0
  324. package/src/utils/overflow.ts +137 -0
  325. package/src/utils/parse-bind.ts +54 -0
  326. package/src/utils/provider-response.ts +30 -0
  327. package/src/utils/retry-after.ts +110 -0
  328. package/src/utils/retry.ts +54 -0
  329. package/src/utils/schema/CONSTRAINTS.md +164 -0
  330. package/src/utils/schema/adapt.ts +36 -0
  331. package/src/utils/schema/compatibility.ts +435 -0
  332. package/src/utils/schema/dereference.ts +98 -0
  333. package/src/utils/schema/draft.ts +341 -0
  334. package/src/utils/schema/equality.ts +97 -0
  335. package/src/utils/schema/fields.ts +190 -0
  336. package/src/utils/schema/index.ts +13 -0
  337. package/src/utils/schema/json-schema-validator.ts +577 -0
  338. package/src/utils/schema/meta-validator.ts +167 -0
  339. package/src/utils/schema/normalize.ts +1588 -0
  340. package/src/utils/schema/spill.ts +43 -0
  341. package/src/utils/schema/stamps.ts +97 -0
  342. package/src/utils/schema/types.ts +11 -0
  343. package/src/utils/schema/wire.ts +213 -0
  344. package/src/utils/schema/zod-decontaminate.ts +331 -0
  345. package/src/utils/sse-debug.ts +289 -0
  346. package/src/utils/tool-call-healing.ts +271 -0
  347. package/src/utils/tool-choice.ts +99 -0
  348. package/src/utils/validation.ts +1019 -0
  349. package/src/utils.ts +166 -0
@@ -0,0 +1,500 @@
1
+ /**
2
+ * Mock provider for tests.
3
+ *
4
+ * Implements `Model<"mock">` + `streamMock` so test code can drive
5
+ * pi-agent-core / streamSimple-shaped consumers without an HTTP client.
6
+ *
7
+ * Usage:
8
+ *
9
+ * import { createMockModel, registerMockApi } from "@gajae-code/ai/providers/mock";
10
+ *
11
+ * // 1. Array of responses, one per call.
12
+ * const mock = createMockModel({
13
+ * responses: [
14
+ * { content: [{ type: "toolCall", name: "read", arguments: { path: "/x" } }] },
15
+ * { content: ["done"] },
16
+ * ],
17
+ * });
18
+ *
19
+ * // 2. Async generator — full state-machine power, can await between turns.
20
+ * const mock = createMockModel({
21
+ * responses: (async function* () {
22
+ * yield { content: [{ type: "toolCall", name: "fetch", arguments: { url } }] };
23
+ * // wait for external coordination
24
+ * await externalReady;
25
+ * yield { content: ["got it"] };
26
+ * })(),
27
+ * });
28
+ *
29
+ * // 3. Per-call handler (closure with access to the call).
30
+ * const mock = createMockModel({
31
+ * handler: (context) => ({ content: [`turn ${context.messages.length}`] }),
32
+ * });
33
+ *
34
+ * // 4. Use as a streamFn for agentLoop:
35
+ * await agentLoop(prompts, context, config, undefined, mock.stream).result();
36
+ *
37
+ * // 5. Or register globally and use stream():
38
+ * registerMockApi();
39
+ * stream(mock.model, context, options);
40
+ *
41
+ * // Inspect calls afterwards.
42
+ * expect(mock.calls).toHaveLength(2);
43
+ */
44
+
45
+ import { registerCustomApi } from "../api-registry";
46
+ import type {
47
+ Api,
48
+ AssistantMessage,
49
+ Context,
50
+ Model,
51
+ SimpleStreamOptions,
52
+ StopReason,
53
+ TextContent,
54
+ ThinkingContent,
55
+ ToolCall,
56
+ Usage,
57
+ } from "../types";
58
+ import { AssistantMessageEventStream } from "../utils/event-stream";
59
+
60
+ /** The API string this provider serves. */
61
+ export const MOCK_API = "mock" as const;
62
+ export type MockApi = typeof MOCK_API;
63
+
64
+ /** Shorthand for a single content block. Strings become text blocks. */
65
+ export type MockContent =
66
+ | string
67
+ | { type: "text"; text: string }
68
+ | { type: "thinking"; thinking: string }
69
+ | {
70
+ type: "toolCall";
71
+ /** Optional explicit id; auto-generated when omitted. */
72
+ id?: string;
73
+ name: string;
74
+ /** Object form is preferred; strings are passed through verbatim. */
75
+ arguments: Record<string, unknown> | string;
76
+ };
77
+
78
+ /** One scripted response. */
79
+ export interface MockResponse {
80
+ /** Content blocks to emit, in order. Strings become text blocks. */
81
+ content?: ReadonlyArray<MockContent>;
82
+ /** Stop reason. Defaults to `"toolUse"` when content has tool calls, else `"stop"`. */
83
+ stopReason?: StopReason;
84
+ /** Usage stats. Missing fields default to 0; missing `cost.total` is recomputed from components. */
85
+ usage?: Partial<Omit<Usage, "cost">> & { cost?: Partial<Usage["cost"]> };
86
+ /** Pre-set responseId. */
87
+ responseId?: string;
88
+ /** If set, the stream emits a terminal error event instead of completing. */
89
+ throw?: string | Error;
90
+ /** Delay before any event is emitted. Honors the call's AbortSignal. */
91
+ delayMs?: number;
92
+ /**
93
+ * If set, the mock synthesizes a {@link ProviderResponseMetadata} and fires
94
+ * `options.onResponse` once before streaming events. Headers are forwarded
95
+ * verbatim (keys lowercased to match real provider plumbing).
96
+ */
97
+ responseHeaders?: Readonly<Record<string, string>>;
98
+ /** HTTP status code paired with {@link responseHeaders}. Defaults to 200. */
99
+ responseStatus?: number;
100
+ /** Pre-set requestId surfaced via {@link ProviderResponseMetadata.requestId}. */
101
+ responseRequestId?: string;
102
+ }
103
+
104
+ /** Handler resolved per call: static script or function. */
105
+ export type MockHandler =
106
+ | MockResponse
107
+ | ((context: Context, options?: SimpleStreamOptions) => MockResponse | Promise<MockResponse>);
108
+
109
+ /**
110
+ * A source of handlers, one per call.
111
+ *
112
+ * - Arrays / iterables consume one entry per call (most ergonomic for scripts).
113
+ * - Async iterables (e.g. `async function*()`) let the test pause between calls,
114
+ * coordinate with external events, or build state machines.
115
+ *
116
+ * The first call pulls the first entry, the second call pulls the second, and
117
+ * so on. When the source is exhausted, `MockModelOptions.handler` is used; if
118
+ * neither is set, the call rejects.
119
+ */
120
+ export type MockResponseSource = Iterable<MockHandler> | AsyncIterable<MockHandler>;
121
+
122
+ /** Recorded call for inspection. */
123
+ export interface MockCall {
124
+ readonly context: Context;
125
+ readonly options?: SimpleStreamOptions;
126
+ }
127
+
128
+ /** Construction options. */
129
+ export interface MockModelOptions {
130
+ /** Model id. Defaults to `"mock-model"`. */
131
+ id?: string;
132
+ /** Provider string used in the returned AssistantMessage. Defaults to `"mock"`. */
133
+ provider?: string;
134
+ /** A sequence of responses, one per call. Accepts arrays, generators, or any iterable. */
135
+ responses?: MockResponseSource;
136
+ /** Fallback handler used when `responses` is exhausted. */
137
+ handler?: MockHandler;
138
+ /** Cost per million tokens. Defaults to zeros. */
139
+ cost?: Model["cost"];
140
+ /** Context window. Defaults to 200_000. */
141
+ contextWindow?: number;
142
+ /** Max output tokens. Defaults to 32_768. */
143
+ maxTokens?: number;
144
+ /** Whether the model claims to support reasoning. Defaults to false. */
145
+ reasoning?: boolean;
146
+ }
147
+
148
+ const ZERO_COST: Model["cost"] = {
149
+ input: 0,
150
+ output: 0,
151
+ cacheRead: 0,
152
+ cacheWrite: 0,
153
+ };
154
+
155
+ /**
156
+ * A `Model<"mock">` that carries its own scripted state. Pass instances to
157
+ * `stream()` or agent configs, and use the same instance to inspect calls
158
+ * and feed additional handlers.
159
+ */
160
+ export class MockModel implements Model<MockApi> {
161
+ readonly id: string;
162
+ readonly name: string;
163
+ readonly api: MockApi = MOCK_API;
164
+ readonly provider: string;
165
+ readonly baseUrl = "mock://";
166
+ readonly reasoning: boolean;
167
+ readonly input: ("text" | "image")[] = ["text"];
168
+ readonly cost: Model["cost"];
169
+ readonly contextWindow: number;
170
+ readonly maxTokens: number;
171
+
172
+ /** Recorded calls in invocation order. */
173
+ readonly calls: MockCall[] = [];
174
+
175
+ iterator?: Iterator<MockHandler> | AsyncIterator<MockHandler>;
176
+ exhausted: boolean;
177
+ readonly extras: MockHandler[] = [];
178
+ fallback?: MockHandler;
179
+ toolCallCounter = 0;
180
+
181
+ constructor(options: MockModelOptions = {}) {
182
+ this.id = options.id ?? "mock-model";
183
+ this.name = options.id ?? "mock-model";
184
+ this.provider = options.provider ?? "mock";
185
+ this.reasoning = options.reasoning ?? false;
186
+ this.cost = options.cost ?? ZERO_COST;
187
+ this.contextWindow = options.contextWindow ?? 200_000;
188
+ this.maxTokens = options.maxTokens ?? 32_768;
189
+ this.iterator = options.responses === undefined ? undefined : iteratorOf(options.responses);
190
+ this.exhausted = options.responses === undefined;
191
+ this.fallback = options.handler;
192
+ }
193
+
194
+ /** Back-compat alias: the model is its own handle. */
195
+ get model(): this {
196
+ return this;
197
+ }
198
+
199
+ /** A streamFn-compatible callable. Forward to `agentLoop` or pi `stream()`. */
200
+ stream = (_model: Model<Api>, context: Context, options?: SimpleStreamOptions): AssistantMessageEventStream =>
201
+ streamMock(this, context, options);
202
+
203
+ /**
204
+ * Append a handler to the internal queue consumed AFTER the constructor
205
+ * `responses` source is exhausted (but before the fallback). Use this for
206
+ * interactive tests that decide responses after the model is created.
207
+ */
208
+ push(response: MockHandler): void {
209
+ this.extras.push(response);
210
+ }
211
+
212
+ /** Reset recorded calls AND the extras queue. The constructor `responses` are NOT reset. */
213
+ reset(): void {
214
+ this.extras.length = 0;
215
+ this.calls.length = 0;
216
+ this.toolCallCounter = 0;
217
+ }
218
+ }
219
+
220
+ /** @deprecated Use {@link MockModel}; the class IS the handle. */
221
+ export type MockModelHandle = MockModel;
222
+
223
+ /** Check whether `model` was produced by `createMockModel`. */
224
+ export function isMockModel(model: Model<Api>): model is MockModel {
225
+ return model instanceof MockModel;
226
+ }
227
+
228
+ /** Construct a mock model. */
229
+ export function createMockModel(options: MockModelOptions = {}): MockModel {
230
+ return new MockModel(options);
231
+ }
232
+
233
+ /** Stream function for `Model<"mock">`. Matches the pi-ai per-provider stream signature. */
234
+ export function streamMock(
235
+ model: Model<Api>,
236
+ context: Context,
237
+ options?: SimpleStreamOptions,
238
+ ): AssistantMessageEventStream {
239
+ const stream = new AssistantMessageEventStream();
240
+ if (!isMockModel(model)) {
241
+ queueMicrotask(() => {
242
+ stream.fail(
243
+ new Error(
244
+ "streamMock called with a model not produced by createMockModel(). " + "Pass a MockModel instance.",
245
+ ),
246
+ );
247
+ });
248
+ return stream;
249
+ }
250
+
251
+ model.calls.push({ context, options });
252
+ void runMock(stream, model, context, options);
253
+ return stream;
254
+ }
255
+
256
+ /** Convenience: register the mock provider with the global custom API registry. */
257
+ export function registerMockApi(sourceId = "pi-ai/mock"): void {
258
+ registerCustomApi(MOCK_API, streamMock, sourceId);
259
+ }
260
+
261
+ // =============================================================================
262
+ // Internal
263
+ // =============================================================================
264
+
265
+ function iteratorOf(source: MockResponseSource): Iterator<MockHandler> | AsyncIterator<MockHandler> {
266
+ if (Symbol.asyncIterator in source) {
267
+ return (source as AsyncIterable<MockHandler>)[Symbol.asyncIterator]();
268
+ }
269
+ return (source as Iterable<MockHandler>)[Symbol.iterator]();
270
+ }
271
+
272
+ async function pullHandler(state: MockModel): Promise<MockHandler | undefined> {
273
+ if (state.iterator && !state.exhausted) {
274
+ const result = await Promise.resolve(state.iterator.next());
275
+ if (!result.done) return result.value;
276
+ state.exhausted = true;
277
+ }
278
+ if (state.extras.length > 0) return state.extras.shift();
279
+ return state.fallback;
280
+ }
281
+
282
+ async function runMock(
283
+ stream: AssistantMessageEventStream,
284
+ model: MockModel,
285
+ context: Context,
286
+ options: SimpleStreamOptions | undefined,
287
+ ): Promise<void> {
288
+ const startedAt = Date.now();
289
+
290
+ let handler: MockHandler | undefined;
291
+ try {
292
+ handler = await pullHandler(model);
293
+ } catch (err) {
294
+ stream.fail(err);
295
+ return;
296
+ }
297
+
298
+ if (handler === undefined) {
299
+ stream.fail(
300
+ new Error(
301
+ `Mock model "${model.id}" received call ${model.calls.length} but no response or handler is configured.`,
302
+ ),
303
+ );
304
+ return;
305
+ }
306
+
307
+ let response: MockResponse;
308
+ try {
309
+ response = typeof handler === "function" ? await handler(context, options) : handler;
310
+ } catch (err) {
311
+ stream.fail(err);
312
+ return;
313
+ }
314
+
315
+ if (response.responseHeaders && options?.onResponse) {
316
+ const headers: Record<string, string> = {};
317
+ for (const [key, value] of Object.entries(response.responseHeaders)) {
318
+ headers[key.toLowerCase()] = value;
319
+ }
320
+ try {
321
+ await options.onResponse(
322
+ {
323
+ status: response.responseStatus ?? 200,
324
+ headers,
325
+ ...(response.responseRequestId !== undefined ? { requestId: response.responseRequestId } : {}),
326
+ },
327
+ model,
328
+ );
329
+ } catch (err) {
330
+ stream.fail(err);
331
+ return;
332
+ }
333
+ }
334
+
335
+ if (response.delayMs && response.delayMs > 0) {
336
+ try {
337
+ await sleep(response.delayMs, options?.signal);
338
+ } catch {
339
+ emitTerminalError(stream, model, startedAt, "aborted", "Mock aborted during delay.");
340
+ return;
341
+ }
342
+ }
343
+
344
+ if (response.throw !== undefined) {
345
+ const message =
346
+ typeof response.throw === "string"
347
+ ? response.throw
348
+ : response.throw instanceof Error
349
+ ? response.throw.message
350
+ : String(response.throw);
351
+ emitTerminalError(stream, model, startedAt, "error", message);
352
+ return;
353
+ }
354
+
355
+ const blocks: Array<TextContent | ThinkingContent | ToolCall> = [];
356
+ const partial: AssistantMessage = {
357
+ role: "assistant",
358
+ content: blocks,
359
+ api: model.api,
360
+ provider: model.provider,
361
+ model: model.id,
362
+ responseId: response.responseId,
363
+ usage: emptyUsage(),
364
+ stopReason: "stop",
365
+ timestamp: startedAt,
366
+ };
367
+
368
+ stream.push({ type: "start", partial });
369
+
370
+ for (const input of response.content ?? []) {
371
+ const block = normalizeContent(input, model);
372
+ blocks.push(block);
373
+ const contentIndex = blocks.length - 1;
374
+
375
+ if (block.type === "text") {
376
+ stream.push({ type: "text_start", contentIndex, partial });
377
+ stream.push({ type: "text_delta", contentIndex, delta: block.text, partial });
378
+ stream.push({ type: "text_end", contentIndex, content: block.text, partial });
379
+ } else if (block.type === "thinking") {
380
+ stream.push({ type: "thinking_start", contentIndex, partial });
381
+ stream.push({ type: "thinking_delta", contentIndex, delta: block.thinking, partial });
382
+ stream.push({ type: "thinking_end", contentIndex, content: block.thinking, partial });
383
+ } else {
384
+ const serialized = typeof block.arguments === "string" ? block.arguments : JSON.stringify(block.arguments);
385
+ stream.push({ type: "toolcall_start", contentIndex, partial });
386
+ stream.push({ type: "toolcall_delta", contentIndex, delta: serialized, partial });
387
+ stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial });
388
+ }
389
+ }
390
+
391
+ const hasToolCall = blocks.some(b => b.type === "toolCall");
392
+ const reason: StopReason = response.stopReason ?? (hasToolCall ? ("toolUse" as StopReason) : ("stop" as StopReason));
393
+
394
+ partial.stopReason = reason;
395
+ partial.usage = mergeUsage(response.usage);
396
+ partial.duration = Date.now() - startedAt;
397
+
398
+ if (reason === "aborted" || reason === "error") {
399
+ stream.push({
400
+ type: "error",
401
+ reason,
402
+ error: { ...partial, errorMessage: partial.errorMessage ?? "mock error" },
403
+ });
404
+ return;
405
+ }
406
+ stream.push({ type: "done", reason: reason as "stop" | "length" | "toolUse", message: partial });
407
+ }
408
+
409
+ function normalizeContent(input: MockContent, state: MockModel): TextContent | ThinkingContent | ToolCall {
410
+ if (typeof input === "string") {
411
+ return { type: "text", text: input };
412
+ }
413
+ if (input.type === "toolCall") {
414
+ return {
415
+ type: "toolCall",
416
+ id: input.id ?? generateToolCallId(state),
417
+ name: input.name,
418
+ arguments: typeof input.arguments === "string" ? input.arguments : { ...input.arguments },
419
+ } as ToolCall;
420
+ }
421
+ return input;
422
+ }
423
+
424
+ function emptyUsage(): Usage {
425
+ return {
426
+ input: 0,
427
+ output: 0,
428
+ cacheRead: 0,
429
+ cacheWrite: 0,
430
+ totalTokens: 0,
431
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
432
+ } as Usage;
433
+ }
434
+
435
+ function mergeUsage(partial?: Partial<Omit<Usage, "cost">> & { cost?: Partial<Usage["cost"]> }): Usage {
436
+ const base = emptyUsage();
437
+ if (!partial) return base;
438
+ const merged = { ...base, ...partial } as Usage;
439
+ const costProvided = partial.cost !== undefined;
440
+ if (costProvided) {
441
+ merged.cost = { ...base.cost, ...partial.cost } as Usage["cost"];
442
+ }
443
+ // Recompute totalTokens when not explicitly provided (canonical formula matches types.ts:
444
+ // input + output + cacheRead + cacheWrite).
445
+ if (partial.totalTokens === undefined) {
446
+ merged.totalTokens = merged.input + merged.output + merged.cacheRead + merged.cacheWrite;
447
+ }
448
+ // Recompute cost.total when cost components were supplied without an explicit total.
449
+ if (costProvided && partial.cost?.total === undefined) {
450
+ merged.cost.total = merged.cost.input + merged.cost.output + merged.cost.cacheRead + merged.cost.cacheWrite;
451
+ }
452
+ return merged;
453
+ }
454
+
455
+ function emitTerminalError(
456
+ stream: AssistantMessageEventStream,
457
+ model: Model<Api>,
458
+ startedAt: number,
459
+ reason: "aborted" | "error",
460
+ message: string,
461
+ ): void {
462
+ const failure: AssistantMessage = {
463
+ role: "assistant",
464
+ content: [],
465
+ api: model.api,
466
+ provider: model.provider,
467
+ model: model.id,
468
+ usage: emptyUsage(),
469
+ stopReason: reason as StopReason,
470
+ errorMessage: message,
471
+ timestamp: startedAt,
472
+ duration: Date.now() - startedAt,
473
+ };
474
+ stream.push({ type: "start", partial: failure });
475
+ stream.push({ type: "error", reason, error: failure });
476
+ }
477
+
478
+ function sleep(ms: number, signal?: AbortSignal): Promise<void> {
479
+ const { promise, resolve, reject } = Promise.withResolvers<void>();
480
+ if (signal?.aborted) {
481
+ reject(signal.reason);
482
+ return promise;
483
+ }
484
+ const onAbort = () => {
485
+ clearTimeout(timer);
486
+ signal?.removeEventListener("abort", onAbort);
487
+ reject(signal?.reason ?? new Error("aborted"));
488
+ };
489
+ const timer = setTimeout(() => {
490
+ signal?.removeEventListener("abort", onAbort);
491
+ resolve();
492
+ }, ms);
493
+ signal?.addEventListener("abort", onAbort, { once: true });
494
+ return promise;
495
+ }
496
+
497
+ function generateToolCallId(state: MockModel): string {
498
+ state.toolCallCounter += 1;
499
+ return `mock-tc-${state.toolCallCounter}`;
500
+ }