@panticonic/pi-ai 0.99.2-vibestudio.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1938 -0
- package/SOURCE.json +8858 -0
- package/dist/api/anthropic-messages.d.ts +71 -0
- package/dist/api/anthropic-messages.d.ts.map +1 -0
- package/dist/api/anthropic-messages.js +1309 -0
- package/dist/api/anthropic-messages.js.map +1 -0
- package/dist/api/anthropic-messages.lazy.d.ts +3 -0
- package/dist/api/anthropic-messages.lazy.d.ts.map +1 -0
- package/dist/api/anthropic-messages.lazy.js +3 -0
- package/dist/api/anthropic-messages.lazy.js.map +1 -0
- package/dist/api/azure-openai-responses.d.ts +17 -0
- package/dist/api/azure-openai-responses.d.ts.map +1 -0
- package/dist/api/azure-openai-responses.js +262 -0
- package/dist/api/azure-openai-responses.js.map +1 -0
- package/dist/api/azure-openai-responses.lazy.d.ts +3 -0
- package/dist/api/azure-openai-responses.lazy.d.ts.map +1 -0
- package/dist/api/azure-openai-responses.lazy.js +3 -0
- package/dist/api/azure-openai-responses.lazy.js.map +1 -0
- package/dist/api/bedrock-converse-stream.d.ts +38 -0
- package/dist/api/bedrock-converse-stream.d.ts.map +1 -0
- package/dist/api/bedrock-converse-stream.js +1093 -0
- package/dist/api/bedrock-converse-stream.js.map +1 -0
- package/dist/api/bedrock-converse-stream.lazy.d.ts +9 -0
- package/dist/api/bedrock-converse-stream.lazy.d.ts.map +1 -0
- package/dist/api/bedrock-converse-stream.lazy.js +30 -0
- package/dist/api/bedrock-converse-stream.lazy.js.map +1 -0
- package/dist/api/cloudflare-ai-binding.d.ts +77 -0
- package/dist/api/cloudflare-ai-binding.d.ts.map +1 -0
- package/dist/api/cloudflare-ai-binding.js +72 -0
- package/dist/api/cloudflare-ai-binding.js.map +1 -0
- package/dist/api/cloudflare-workers-ai-system-one.d.ts +4 -0
- package/dist/api/cloudflare-workers-ai-system-one.d.ts.map +1 -0
- package/dist/api/cloudflare-workers-ai-system-one.js +43 -0
- package/dist/api/cloudflare-workers-ai-system-one.js.map +1 -0
- package/dist/api/cloudflare-workers-ai-system-one.lazy.d.ts +3 -0
- package/dist/api/cloudflare-workers-ai-system-one.lazy.d.ts.map +1 -0
- package/dist/api/cloudflare-workers-ai-system-one.lazy.js +4 -0
- package/dist/api/cloudflare-workers-ai-system-one.lazy.js.map +1 -0
- package/dist/api/cloudflare.d.ts +11 -0
- package/dist/api/cloudflare.d.ts.map +1 -0
- package/dist/api/cloudflare.js +11 -0
- package/dist/api/cloudflare.js.map +1 -0
- package/dist/api/constrained-sampling.d.ts +26 -0
- package/dist/api/constrained-sampling.d.ts.map +1 -0
- package/dist/api/constrained-sampling.js +233 -0
- package/dist/api/constrained-sampling.js.map +1 -0
- package/dist/api/github-copilot-headers.d.ts +8 -0
- package/dist/api/github-copilot-headers.d.ts.map +1 -0
- package/dist/api/github-copilot-headers.js +29 -0
- package/dist/api/github-copilot-headers.js.map +1 -0
- package/dist/api/google-generative-ai.d.ts +13 -0
- package/dist/api/google-generative-ai.d.ts.map +1 -0
- package/dist/api/google-generative-ai.js +370 -0
- package/dist/api/google-generative-ai.js.map +1 -0
- package/dist/api/google-generative-ai.lazy.d.ts +3 -0
- package/dist/api/google-generative-ai.lazy.d.ts.map +1 -0
- package/dist/api/google-generative-ai.lazy.js +3 -0
- package/dist/api/google-generative-ai.lazy.js.map +1 -0
- package/dist/api/google-shared.d.ts +93 -0
- package/dist/api/google-shared.d.ts.map +1 -0
- package/dist/api/google-shared.js +451 -0
- package/dist/api/google-shared.js.map +1 -0
- package/dist/api/google-vertex.d.ts +15 -0
- package/dist/api/google-vertex.d.ts.map +1 -0
- package/dist/api/google-vertex.js +431 -0
- package/dist/api/google-vertex.js.map +1 -0
- package/dist/api/google-vertex.lazy.d.ts +3 -0
- package/dist/api/google-vertex.lazy.d.ts.map +1 -0
- package/dist/api/google-vertex.lazy.js +3 -0
- package/dist/api/google-vertex.lazy.js.map +1 -0
- package/dist/api/lazy.d.ts +19 -0
- package/dist/api/lazy.d.ts.map +1 -0
- package/dist/api/lazy.js +70 -0
- package/dist/api/lazy.js.map +1 -0
- package/dist/api/llama-cpp-classify.d.ts +33 -0
- package/dist/api/llama-cpp-classify.d.ts.map +1 -0
- package/dist/api/llama-cpp-classify.js +365 -0
- package/dist/api/llama-cpp-classify.js.map +1 -0
- package/dist/api/llama-cpp-classify.lazy.d.ts +3 -0
- package/dist/api/llama-cpp-classify.lazy.d.ts.map +1 -0
- package/dist/api/llama-cpp-classify.lazy.js +4 -0
- package/dist/api/llama-cpp-classify.lazy.js.map +1 -0
- package/dist/api/mistral-conversations.d.ts +25 -0
- package/dist/api/mistral-conversations.d.ts.map +1 -0
- package/dist/api/mistral-conversations.js +749 -0
- package/dist/api/mistral-conversations.js.map +1 -0
- package/dist/api/mistral-conversations.lazy.d.ts +3 -0
- package/dist/api/mistral-conversations.lazy.d.ts.map +1 -0
- package/dist/api/mistral-conversations.lazy.js +3 -0
- package/dist/api/mistral-conversations.lazy.js.map +1 -0
- package/dist/api/openai-codex-responses.d.ts +32 -0
- package/dist/api/openai-codex-responses.d.ts.map +1 -0
- package/dist/api/openai-codex-responses.js +1374 -0
- package/dist/api/openai-codex-responses.js.map +1 -0
- package/dist/api/openai-codex-responses.lazy.d.ts +3 -0
- package/dist/api/openai-codex-responses.lazy.d.ts.map +1 -0
- package/dist/api/openai-codex-responses.lazy.js +3 -0
- package/dist/api/openai-codex-responses.lazy.js.map +1 -0
- package/dist/api/openai-completions.d.ts +26 -0
- package/dist/api/openai-completions.d.ts.map +1 -0
- package/dist/api/openai-completions.js +1380 -0
- package/dist/api/openai-completions.js.map +1 -0
- package/dist/api/openai-completions.lazy.d.ts +3 -0
- package/dist/api/openai-completions.lazy.d.ts.map +1 -0
- package/dist/api/openai-completions.lazy.js +3 -0
- package/dist/api/openai-completions.lazy.js.map +1 -0
- package/dist/api/openai-prompt-cache.d.ts +3 -0
- package/dist/api/openai-prompt-cache.d.ts.map +1 -0
- package/dist/api/openai-prompt-cache.js +10 -0
- package/dist/api/openai-prompt-cache.js.map +1 -0
- package/dist/api/openai-responses-shared.d.ts +29 -0
- package/dist/api/openai-responses-shared.d.ts.map +1 -0
- package/dist/api/openai-responses-shared.js +703 -0
- package/dist/api/openai-responses-shared.js.map +1 -0
- package/dist/api/openai-responses.d.ts +14 -0
- package/dist/api/openai-responses.d.ts.map +1 -0
- package/dist/api/openai-responses.js +310 -0
- package/dist/api/openai-responses.js.map +1 -0
- package/dist/api/openai-responses.lazy.d.ts +3 -0
- package/dist/api/openai-responses.lazy.d.ts.map +1 -0
- package/dist/api/openai-responses.lazy.js +3 -0
- package/dist/api/openai-responses.lazy.js.map +1 -0
- package/dist/api/openrouter-images.d.ts +4 -0
- package/dist/api/openrouter-images.d.ts.map +1 -0
- package/dist/api/openrouter-images.js +133 -0
- package/dist/api/openrouter-images.js.map +1 -0
- package/dist/api/openrouter-images.lazy.d.ts +3 -0
- package/dist/api/openrouter-images.lazy.d.ts.map +1 -0
- package/dist/api/openrouter-images.lazy.js +4 -0
- package/dist/api/openrouter-images.lazy.js.map +1 -0
- package/dist/api/pi-messages.d.ts +99 -0
- package/dist/api/pi-messages.d.ts.map +1 -0
- package/dist/api/pi-messages.js +314 -0
- package/dist/api/pi-messages.js.map +1 -0
- package/dist/api/pi-messages.lazy.d.ts +3 -0
- package/dist/api/pi-messages.lazy.d.ts.map +1 -0
- package/dist/api/pi-messages.lazy.js +3 -0
- package/dist/api/pi-messages.lazy.js.map +1 -0
- package/dist/api/simple-options.d.ts +15 -0
- package/dist/api/simple-options.d.ts.map +1 -0
- package/dist/api/simple-options.js +66 -0
- package/dist/api/simple-options.js.map +1 -0
- package/dist/api/system-one-shared.d.ts +23 -0
- package/dist/api/system-one-shared.d.ts.map +1 -0
- package/dist/api/system-one-shared.js +183 -0
- package/dist/api/system-one-shared.js.map +1 -0
- package/dist/api/transform-messages.d.ts +8 -0
- package/dist/api/transform-messages.d.ts.map +1 -0
- package/dist/api/transform-messages.js +201 -0
- package/dist/api/transform-messages.js.map +1 -0
- package/dist/api/typesafe-system-one.d.ts +4 -0
- package/dist/api/typesafe-system-one.d.ts.map +1 -0
- package/dist/api/typesafe-system-one.js +19 -0
- package/dist/api/typesafe-system-one.js.map +1 -0
- package/dist/api/typesafe-system-one.lazy.d.ts +3 -0
- package/dist/api/typesafe-system-one.lazy.d.ts.map +1 -0
- package/dist/api/typesafe-system-one.lazy.js +4 -0
- package/dist/api/typesafe-system-one.lazy.js.map +1 -0
- package/dist/auth/context.d.ts +7 -0
- package/dist/auth/context.d.ts.map +1 -0
- package/dist/auth/context.js +42 -0
- package/dist/auth/context.js.map +1 -0
- package/dist/auth/credential-store.d.ts +17 -0
- package/dist/auth/credential-store.d.ts.map +1 -0
- package/dist/auth/credential-store.js +51 -0
- package/dist/auth/credential-store.js.map +1 -0
- package/dist/auth/helpers.d.ts +22 -0
- package/dist/auth/helpers.d.ts.map +1 -0
- package/dist/auth/helpers.js +53 -0
- package/dist/auth/helpers.js.map +1 -0
- package/dist/auth/oauth/anthropic.d.ts +9 -0
- package/dist/auth/oauth/anthropic.d.ts.map +1 -0
- package/dist/auth/oauth/anthropic.js +249 -0
- package/dist/auth/oauth/anthropic.js.map +1 -0
- package/dist/auth/oauth/callback-server.d.ts +55 -0
- package/dist/auth/oauth/callback-server.d.ts.map +1 -0
- package/dist/auth/oauth/callback-server.js +146 -0
- package/dist/auth/oauth/callback-server.js.map +1 -0
- package/dist/auth/oauth/device-code.d.ts +24 -0
- package/dist/auth/oauth/device-code.d.ts.map +1 -0
- package/dist/auth/oauth/device-code.js +69 -0
- package/dist/auth/oauth/device-code.js.map +1 -0
- package/dist/auth/oauth/github-copilot.d.ts +6 -0
- package/dist/auth/oauth/github-copilot.d.ts.map +1 -0
- package/dist/auth/oauth/github-copilot.js +392 -0
- package/dist/auth/oauth/github-copilot.js.map +1 -0
- package/dist/auth/oauth/kimi-coding.d.ts +10 -0
- package/dist/auth/oauth/kimi-coding.d.ts.map +1 -0
- package/dist/auth/oauth/kimi-coding.js +248 -0
- package/dist/auth/oauth/kimi-coding.js.map +1 -0
- package/dist/auth/oauth/load.d.ts +31 -0
- package/dist/auth/oauth/load.d.ts.map +1 -0
- package/dist/auth/oauth/load.js +69 -0
- package/dist/auth/oauth/load.js.map +1 -0
- package/dist/auth/oauth/meta.d.ts +17 -0
- package/dist/auth/oauth/meta.d.ts.map +1 -0
- package/dist/auth/oauth/meta.js +190 -0
- package/dist/auth/oauth/meta.js.map +1 -0
- package/dist/auth/oauth/openai-chatgpt.d.ts +9 -0
- package/dist/auth/oauth/openai-chatgpt.d.ts.map +1 -0
- package/dist/auth/oauth/openai-chatgpt.js +266 -0
- package/dist/auth/oauth/openai-chatgpt.js.map +1 -0
- package/dist/auth/oauth/openai-codex.d.ts +9 -0
- package/dist/auth/oauth/openai-codex.d.ts.map +1 -0
- package/dist/auth/oauth/openai-codex.js +346 -0
- package/dist/auth/oauth/openai-codex.js.map +1 -0
- package/dist/auth/oauth/openrouter.d.ts +15 -0
- package/dist/auth/oauth/openrouter.d.ts.map +1 -0
- package/dist/auth/oauth/openrouter.js +158 -0
- package/dist/auth/oauth/openrouter.js.map +1 -0
- package/dist/auth/oauth/pkce.d.ts +13 -0
- package/dist/auth/oauth/pkce.d.ts.map +1 -0
- package/dist/auth/oauth/pkce.js +31 -0
- package/dist/auth/oauth/pkce.js.map +1 -0
- package/dist/auth/oauth/radius.d.ts +17 -0
- package/dist/auth/oauth/radius.d.ts.map +1 -0
- package/dist/auth/oauth/radius.js +253 -0
- package/dist/auth/oauth/radius.js.map +1 -0
- package/dist/auth/oauth/xai.d.ts +6 -0
- package/dist/auth/oauth/xai.d.ts.map +1 -0
- package/dist/auth/oauth/xai.js +190 -0
- package/dist/auth/oauth/xai.js.map +1 -0
- package/dist/auth/resolve.d.ts +21 -0
- package/dist/auth/resolve.d.ts.map +1 -0
- package/dist/auth/resolve.js +116 -0
- package/dist/auth/resolve.js.map +1 -0
- package/dist/auth/types.d.ts +240 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +2 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/bedrock-provider.d.ts +5 -0
- package/dist/bedrock-provider.d.ts.map +1 -0
- package/dist/bedrock-provider.js +6 -0
- package/dist/bedrock-provider.js.map +1 -0
- package/dist/bun-oauth.d.ts +3 -0
- package/dist/bun-oauth.d.ts.map +1 -0
- package/dist/bun-oauth.js +25 -0
- package/dist/bun-oauth.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +115 -0
- package/dist/cli.js.map +1 -0
- package/dist/compat/extension-oauth-types.d.ts +39 -0
- package/dist/compat/extension-oauth-types.d.ts.map +1 -0
- package/dist/compat/extension-oauth-types.js +2 -0
- package/dist/compat/extension-oauth-types.js.map +1 -0
- package/dist/compat.d.ts +67 -0
- package/dist/compat.d.ts.map +1 -0
- package/dist/compat.js +202 -0
- package/dist/compat.js.map +1 -0
- package/dist/env-api-keys.d.ts +26 -0
- package/dist/env-api-keys.d.ts.map +1 -0
- package/dist/env-api-keys.js +163 -0
- package/dist/env-api-keys.js.map +1 -0
- package/dist/image-models.d.ts +21 -0
- package/dist/image-models.d.ts.map +1 -0
- package/dist/image-models.js +24 -0
- package/dist/image-models.js.map +1 -0
- package/dist/images-api-registry.d.ts +14 -0
- package/dist/images-api-registry.d.ts.map +1 -0
- package/dist/images-api-registry.js +22 -0
- package/dist/images-api-registry.js.map +1 -0
- package/dist/images.d.ts +9 -0
- package/dist/images.d.ts.map +1 -0
- package/dist/images.js +19 -0
- package/dist/images.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/legacy-api-aliases.d.ts +42 -0
- package/dist/legacy-api-aliases.d.ts.map +1 -0
- package/dist/legacy-api-aliases.js +49 -0
- package/dist/legacy-api-aliases.js.map +1 -0
- package/dist/model-catalog.d.ts +33 -0
- package/dist/model-catalog.d.ts.map +1 -0
- package/dist/model-catalog.js +16 -0
- package/dist/model-catalog.js.map +1 -0
- package/dist/models-store.d.ts +30 -0
- package/dist/models-store.d.ts.map +1 -0
- package/dist/models-store.js +17 -0
- package/dist/models-store.js.map +1 -0
- package/dist/models.d.ts +269 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.generated.d.ts +175 -0
- package/dist/models.generated.d.ts.map +1 -0
- package/dist/models.generated.js +177 -0
- package/dist/models.generated.js.map +1 -0
- package/dist/models.js +721 -0
- package/dist/models.js.map +1 -0
- package/dist/oauth.d.ts +3 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +2 -0
- package/dist/oauth.js.map +1 -0
- package/dist/providers/all.d.ts +34 -0
- package/dist/providers/all.d.ts.map +1 -0
- package/dist/providers/all.js +137 -0
- package/dist/providers/all.js.map +1 -0
- package/dist/providers/amazon-bedrock.d.ts +3 -0
- package/dist/providers/amazon-bedrock.d.ts.map +1 -0
- package/dist/providers/amazon-bedrock.js +93 -0
- package/dist/providers/amazon-bedrock.js.map +1 -0
- package/dist/providers/amazon-bedrock.models.d.ts +6 -0
- package/dist/providers/amazon-bedrock.models.d.ts.map +1 -0
- package/dist/providers/amazon-bedrock.models.js +8 -0
- package/dist/providers/amazon-bedrock.models.js.map +1 -0
- package/dist/providers/ant-ling.d.ts +3 -0
- package/dist/providers/ant-ling.d.ts.map +1 -0
- package/dist/providers/ant-ling.js +15 -0
- package/dist/providers/ant-ling.js.map +1 -0
- package/dist/providers/ant-ling.models.d.ts +6 -0
- package/dist/providers/ant-ling.models.d.ts.map +1 -0
- package/dist/providers/ant-ling.models.js +8 -0
- package/dist/providers/ant-ling.models.js.map +1 -0
- package/dist/providers/anthropic.d.ts +3 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +78 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/anthropic.models.d.ts +6 -0
- package/dist/providers/anthropic.models.d.ts.map +1 -0
- package/dist/providers/anthropic.models.js +8 -0
- package/dist/providers/anthropic.models.js.map +1 -0
- package/dist/providers/azure-openai-responses.d.ts +3 -0
- package/dist/providers/azure-openai-responses.d.ts.map +1 -0
- package/dist/providers/azure-openai-responses.js +14 -0
- package/dist/providers/azure-openai-responses.js.map +1 -0
- package/dist/providers/azure-openai-responses.models.d.ts +6 -0
- package/dist/providers/azure-openai-responses.models.d.ts.map +1 -0
- package/dist/providers/azure-openai-responses.models.js +8 -0
- package/dist/providers/azure-openai-responses.models.js.map +1 -0
- package/dist/providers/baseten.d.ts +3 -0
- package/dist/providers/baseten.d.ts.map +1 -0
- package/dist/providers/baseten.js +15 -0
- package/dist/providers/baseten.js.map +1 -0
- package/dist/providers/baseten.models.d.ts +6 -0
- package/dist/providers/baseten.models.d.ts.map +1 -0
- package/dist/providers/baseten.models.js +8 -0
- package/dist/providers/baseten.models.js.map +1 -0
- package/dist/providers/cerebras.d.ts +3 -0
- package/dist/providers/cerebras.d.ts.map +1 -0
- package/dist/providers/cerebras.js +15 -0
- package/dist/providers/cerebras.js.map +1 -0
- package/dist/providers/cerebras.models.d.ts +6 -0
- package/dist/providers/cerebras.models.d.ts.map +1 -0
- package/dist/providers/cerebras.models.js +8 -0
- package/dist/providers/cerebras.models.js.map +1 -0
- package/dist/providers/cloudflare-ai-gateway.d.ts +5 -0
- package/dist/providers/cloudflare-ai-gateway.d.ts.map +1 -0
- package/dist/providers/cloudflare-ai-gateway.js +25 -0
- package/dist/providers/cloudflare-ai-gateway.js.map +1 -0
- package/dist/providers/cloudflare-ai-gateway.models.d.ts +6 -0
- package/dist/providers/cloudflare-ai-gateway.models.d.ts.map +1 -0
- package/dist/providers/cloudflare-ai-gateway.models.js +8 -0
- package/dist/providers/cloudflare-ai-gateway.models.js.map +1 -0
- package/dist/providers/cloudflare-auth.d.ts +4 -0
- package/dist/providers/cloudflare-auth.d.ts.map +1 -0
- package/dist/providers/cloudflare-auth.js +86 -0
- package/dist/providers/cloudflare-auth.js.map +1 -0
- package/dist/providers/cloudflare-stream.d.ts +12 -0
- package/dist/providers/cloudflare-stream.d.ts.map +1 -0
- package/dist/providers/cloudflare-stream.js +27 -0
- package/dist/providers/cloudflare-stream.js.map +1 -0
- package/dist/providers/cloudflare-workers-ai.d.ts +3 -0
- package/dist/providers/cloudflare-workers-ai.d.ts.map +1 -0
- package/dist/providers/cloudflare-workers-ai.js +22 -0
- package/dist/providers/cloudflare-workers-ai.js.map +1 -0
- package/dist/providers/cloudflare-workers-ai.models.d.ts +6 -0
- package/dist/providers/cloudflare-workers-ai.models.d.ts.map +1 -0
- package/dist/providers/cloudflare-workers-ai.models.js +8 -0
- package/dist/providers/cloudflare-workers-ai.models.js.map +1 -0
- package/dist/providers/data/.manifest.json +1 -0
- package/dist/providers/data/amazon-bedrock.json +1 -0
- package/dist/providers/data/ant-ling.json +1 -0
- package/dist/providers/data/anthropic.json +1 -0
- package/dist/providers/data/azure-openai-responses.json +1 -0
- package/dist/providers/data/baseten.json +1 -0
- package/dist/providers/data/cerebras.json +1 -0
- package/dist/providers/data/cloudflare-ai-gateway.json +1 -0
- package/dist/providers/data/cloudflare-workers-ai.json +1 -0
- package/dist/providers/data/deepseek.json +1 -0
- package/dist/providers/data/fireworks.json +1 -0
- package/dist/providers/data/github-copilot.json +1 -0
- package/dist/providers/data/google-vertex.json +1 -0
- package/dist/providers/data/google.json +1 -0
- package/dist/providers/data/groq.json +1 -0
- package/dist/providers/data/huggingface.json +1 -0
- package/dist/providers/data/kimi-coding.json +1 -0
- package/dist/providers/data/meta.json +1 -0
- package/dist/providers/data/minimax-cn.json +1 -0
- package/dist/providers/data/minimax.json +1 -0
- package/dist/providers/data/mistral.json +1 -0
- package/dist/providers/data/moonshotai-cn.json +1 -0
- package/dist/providers/data/moonshotai.json +1 -0
- package/dist/providers/data/nvidia.json +1 -0
- package/dist/providers/data/openai-codex.json +1 -0
- package/dist/providers/data/openai.json +1 -0
- package/dist/providers/data/opencode-go.json +1 -0
- package/dist/providers/data/opencode.json +1 -0
- package/dist/providers/data/openrouter.json +1 -0
- package/dist/providers/data/qwen-token-plan-cn.json +1 -0
- package/dist/providers/data/qwen-token-plan-individual.json +1 -0
- package/dist/providers/data/qwen-token-plan.json +1 -0
- package/dist/providers/data/radius.json +1 -0
- package/dist/providers/data/together.json +1 -0
- package/dist/providers/data/typesafe.json +1 -0
- package/dist/providers/data/vercel-ai-gateway.json +1 -0
- package/dist/providers/data/xai.json +1 -0
- package/dist/providers/data/xiaomi-token-plan-ams.json +1 -0
- package/dist/providers/data/xiaomi-token-plan-cn.json +1 -0
- package/dist/providers/data/xiaomi-token-plan-sgp.json +1 -0
- package/dist/providers/data/xiaomi.json +1 -0
- package/dist/providers/data/zai-coding-cn.json +1 -0
- package/dist/providers/data/zai.json +1 -0
- package/dist/providers/deepseek.d.ts +3 -0
- package/dist/providers/deepseek.d.ts.map +1 -0
- package/dist/providers/deepseek.js +15 -0
- package/dist/providers/deepseek.js.map +1 -0
- package/dist/providers/deepseek.models.d.ts +6 -0
- package/dist/providers/deepseek.models.d.ts.map +1 -0
- package/dist/providers/deepseek.models.js +8 -0
- package/dist/providers/deepseek.models.js.map +1 -0
- package/dist/providers/faux.d.ts +103 -0
- package/dist/providers/faux.d.ts.map +1 -0
- package/dist/providers/faux.js +489 -0
- package/dist/providers/faux.js.map +1 -0
- package/dist/providers/fireworks.d.ts +3 -0
- package/dist/providers/fireworks.d.ts.map +1 -0
- package/dist/providers/fireworks.js +19 -0
- package/dist/providers/fireworks.js.map +1 -0
- package/dist/providers/fireworks.models.d.ts +6 -0
- package/dist/providers/fireworks.models.d.ts.map +1 -0
- package/dist/providers/fireworks.models.js +8 -0
- package/dist/providers/fireworks.models.js.map +1 -0
- package/dist/providers/github-copilot.d.ts +3 -0
- package/dist/providers/github-copilot.d.ts.map +1 -0
- package/dist/providers/github-copilot.js +35 -0
- package/dist/providers/github-copilot.js.map +1 -0
- package/dist/providers/github-copilot.models.d.ts +6 -0
- package/dist/providers/github-copilot.models.d.ts.map +1 -0
- package/dist/providers/github-copilot.models.js +8 -0
- package/dist/providers/github-copilot.models.js.map +1 -0
- package/dist/providers/google-vertex.d.ts +3 -0
- package/dist/providers/google-vertex.d.ts.map +1 -0
- package/dist/providers/google-vertex.js +94 -0
- package/dist/providers/google-vertex.js.map +1 -0
- package/dist/providers/google-vertex.models.d.ts +6 -0
- package/dist/providers/google-vertex.models.d.ts.map +1 -0
- package/dist/providers/google-vertex.models.js +8 -0
- package/dist/providers/google-vertex.models.js.map +1 -0
- package/dist/providers/google.d.ts +3 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +15 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/google.models.d.ts +6 -0
- package/dist/providers/google.models.d.ts.map +1 -0
- package/dist/providers/google.models.js +8 -0
- package/dist/providers/google.models.js.map +1 -0
- package/dist/providers/groq.d.ts +3 -0
- package/dist/providers/groq.d.ts.map +1 -0
- package/dist/providers/groq.js +15 -0
- package/dist/providers/groq.js.map +1 -0
- package/dist/providers/groq.models.d.ts +6 -0
- package/dist/providers/groq.models.d.ts.map +1 -0
- package/dist/providers/groq.models.js +8 -0
- package/dist/providers/groq.models.js.map +1 -0
- package/dist/providers/huggingface.d.ts +3 -0
- package/dist/providers/huggingface.d.ts.map +1 -0
- package/dist/providers/huggingface.js +15 -0
- package/dist/providers/huggingface.js.map +1 -0
- package/dist/providers/huggingface.models.d.ts +6 -0
- package/dist/providers/huggingface.models.d.ts.map +1 -0
- package/dist/providers/huggingface.models.js +8 -0
- package/dist/providers/huggingface.models.js.map +1 -0
- package/dist/providers/images/register-builtins.d.ts +4 -0
- package/dist/providers/images/register-builtins.d.ts.map +1 -0
- package/dist/providers/images/register-builtins.js +34 -0
- package/dist/providers/images/register-builtins.js.map +1 -0
- package/dist/providers/kimi-coding.d.ts +3 -0
- package/dist/providers/kimi-coding.d.ts.map +1 -0
- package/dist/providers/kimi-coding.js +24 -0
- package/dist/providers/kimi-coding.js.map +1 -0
- package/dist/providers/kimi-coding.models.d.ts +6 -0
- package/dist/providers/kimi-coding.models.d.ts.map +1 -0
- package/dist/providers/kimi-coding.models.js +8 -0
- package/dist/providers/kimi-coding.models.js.map +1 -0
- package/dist/providers/meta.d.ts +3 -0
- package/dist/providers/meta.d.ts.map +1 -0
- package/dist/providers/meta.js +24 -0
- package/dist/providers/meta.js.map +1 -0
- package/dist/providers/meta.models.d.ts +6 -0
- package/dist/providers/meta.models.d.ts.map +1 -0
- package/dist/providers/meta.models.js +8 -0
- package/dist/providers/meta.models.js.map +1 -0
- package/dist/providers/minimax-cn.d.ts +3 -0
- package/dist/providers/minimax-cn.d.ts.map +1 -0
- package/dist/providers/minimax-cn.js +15 -0
- package/dist/providers/minimax-cn.js.map +1 -0
- package/dist/providers/minimax-cn.models.d.ts +6 -0
- package/dist/providers/minimax-cn.models.d.ts.map +1 -0
- package/dist/providers/minimax-cn.models.js +8 -0
- package/dist/providers/minimax-cn.models.js.map +1 -0
- package/dist/providers/minimax.d.ts +3 -0
- package/dist/providers/minimax.d.ts.map +1 -0
- package/dist/providers/minimax.js +15 -0
- package/dist/providers/minimax.js.map +1 -0
- package/dist/providers/minimax.models.d.ts +6 -0
- package/dist/providers/minimax.models.d.ts.map +1 -0
- package/dist/providers/minimax.models.js +8 -0
- package/dist/providers/minimax.models.js.map +1 -0
- package/dist/providers/mistral.d.ts +3 -0
- package/dist/providers/mistral.d.ts.map +1 -0
- package/dist/providers/mistral.js +15 -0
- package/dist/providers/mistral.js.map +1 -0
- package/dist/providers/mistral.models.d.ts +6 -0
- package/dist/providers/mistral.models.d.ts.map +1 -0
- package/dist/providers/mistral.models.js +8 -0
- package/dist/providers/mistral.models.js.map +1 -0
- package/dist/providers/moonshotai-cn.d.ts +3 -0
- package/dist/providers/moonshotai-cn.d.ts.map +1 -0
- package/dist/providers/moonshotai-cn.js +15 -0
- package/dist/providers/moonshotai-cn.js.map +1 -0
- package/dist/providers/moonshotai-cn.models.d.ts +6 -0
- package/dist/providers/moonshotai-cn.models.d.ts.map +1 -0
- package/dist/providers/moonshotai-cn.models.js +8 -0
- package/dist/providers/moonshotai-cn.models.js.map +1 -0
- package/dist/providers/moonshotai.d.ts +3 -0
- package/dist/providers/moonshotai.d.ts.map +1 -0
- package/dist/providers/moonshotai.js +15 -0
- package/dist/providers/moonshotai.js.map +1 -0
- package/dist/providers/moonshotai.models.d.ts +6 -0
- package/dist/providers/moonshotai.models.d.ts.map +1 -0
- package/dist/providers/moonshotai.models.js +8 -0
- package/dist/providers/moonshotai.models.js.map +1 -0
- package/dist/providers/nvidia.d.ts +3 -0
- package/dist/providers/nvidia.d.ts.map +1 -0
- package/dist/providers/nvidia.js +15 -0
- package/dist/providers/nvidia.js.map +1 -0
- package/dist/providers/nvidia.models.d.ts +6 -0
- package/dist/providers/nvidia.models.d.ts.map +1 -0
- package/dist/providers/nvidia.models.js +8 -0
- package/dist/providers/nvidia.models.js.map +1 -0
- package/dist/providers/openai-codex.d.ts +3 -0
- package/dist/providers/openai-codex.d.ts.map +1 -0
- package/dist/providers/openai-codex.js +22 -0
- package/dist/providers/openai-codex.js.map +1 -0
- package/dist/providers/openai-codex.models.d.ts +6 -0
- package/dist/providers/openai-codex.models.d.ts.map +1 -0
- package/dist/providers/openai-codex.models.js +8 -0
- package/dist/providers/openai-codex.models.js.map +1 -0
- package/dist/providers/openai.d.ts +3 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +24 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/openai.models.d.ts +6 -0
- package/dist/providers/openai.models.d.ts.map +1 -0
- package/dist/providers/openai.models.js +8 -0
- package/dist/providers/openai.models.js.map +1 -0
- package/dist/providers/opencode-go.d.ts +3 -0
- package/dist/providers/opencode-go.d.ts.map +1 -0
- package/dist/providers/opencode-go.js +21 -0
- package/dist/providers/opencode-go.js.map +1 -0
- package/dist/providers/opencode-go.models.d.ts +6 -0
- package/dist/providers/opencode-go.models.d.ts.map +1 -0
- package/dist/providers/opencode-go.models.js +8 -0
- package/dist/providers/opencode-go.models.js.map +1 -0
- package/dist/providers/opencode-headers.d.ts +4 -0
- package/dist/providers/opencode-headers.d.ts.map +1 -0
- package/dist/providers/opencode-headers.js +22 -0
- package/dist/providers/opencode-headers.js.map +1 -0
- package/dist/providers/opencode.d.ts +5 -0
- package/dist/providers/opencode.d.ts.map +1 -0
- package/dist/providers/opencode.js +26 -0
- package/dist/providers/opencode.js.map +1 -0
- package/dist/providers/opencode.models.d.ts +6 -0
- package/dist/providers/opencode.models.d.ts.map +1 -0
- package/dist/providers/opencode.models.js +8 -0
- package/dist/providers/opencode.models.js.map +1 -0
- package/dist/providers/openrouter.d.ts +3 -0
- package/dist/providers/openrouter.d.ts.map +1 -0
- package/dist/providers/openrouter.js +36 -0
- package/dist/providers/openrouter.js.map +1 -0
- package/dist/providers/openrouter.models.d.ts +6 -0
- package/dist/providers/openrouter.models.d.ts.map +1 -0
- package/dist/providers/openrouter.models.js +8 -0
- package/dist/providers/openrouter.models.js.map +1 -0
- package/dist/providers/qwen-token-plan-cn.d.ts +3 -0
- package/dist/providers/qwen-token-plan-cn.d.ts.map +1 -0
- package/dist/providers/qwen-token-plan-cn.js +15 -0
- package/dist/providers/qwen-token-plan-cn.js.map +1 -0
- package/dist/providers/qwen-token-plan-cn.models.d.ts +6 -0
- package/dist/providers/qwen-token-plan-cn.models.d.ts.map +1 -0
- package/dist/providers/qwen-token-plan-cn.models.js +8 -0
- package/dist/providers/qwen-token-plan-cn.models.js.map +1 -0
- package/dist/providers/qwen-token-plan-individual.d.ts +3 -0
- package/dist/providers/qwen-token-plan-individual.d.ts.map +1 -0
- package/dist/providers/qwen-token-plan-individual.js +15 -0
- package/dist/providers/qwen-token-plan-individual.js.map +1 -0
- package/dist/providers/qwen-token-plan-individual.models.d.ts +6 -0
- package/dist/providers/qwen-token-plan-individual.models.d.ts.map +1 -0
- package/dist/providers/qwen-token-plan-individual.models.js +8 -0
- package/dist/providers/qwen-token-plan-individual.models.js.map +1 -0
- package/dist/providers/qwen-token-plan.d.ts +3 -0
- package/dist/providers/qwen-token-plan.d.ts.map +1 -0
- package/dist/providers/qwen-token-plan.js +15 -0
- package/dist/providers/qwen-token-plan.js.map +1 -0
- package/dist/providers/qwen-token-plan.models.d.ts +6 -0
- package/dist/providers/qwen-token-plan.models.d.ts.map +1 -0
- package/dist/providers/qwen-token-plan.models.js +8 -0
- package/dist/providers/qwen-token-plan.models.js.map +1 -0
- package/dist/providers/radius-config.d.ts +26 -0
- package/dist/providers/radius-config.d.ts.map +1 -0
- package/dist/providers/radius-config.js +63 -0
- package/dist/providers/radius-config.js.map +1 -0
- package/dist/providers/radius.d.ts +9 -0
- package/dist/providers/radius.d.ts.map +1 -0
- package/dist/providers/radius.js +78 -0
- package/dist/providers/radius.js.map +1 -0
- package/dist/providers/radius.models.d.ts +6 -0
- package/dist/providers/radius.models.d.ts.map +1 -0
- package/dist/providers/radius.models.js +8 -0
- package/dist/providers/radius.models.js.map +1 -0
- package/dist/providers/together.d.ts +3 -0
- package/dist/providers/together.d.ts.map +1 -0
- package/dist/providers/together.js +15 -0
- package/dist/providers/together.js.map +1 -0
- package/dist/providers/together.models.d.ts +6 -0
- package/dist/providers/together.models.d.ts.map +1 -0
- package/dist/providers/together.models.js +8 -0
- package/dist/providers/together.models.js.map +1 -0
- package/dist/providers/typesafe.d.ts +3 -0
- package/dist/providers/typesafe.d.ts.map +1 -0
- package/dist/providers/typesafe.js +18 -0
- package/dist/providers/typesafe.js.map +1 -0
- package/dist/providers/typesafe.models.d.ts +6 -0
- package/dist/providers/typesafe.models.d.ts.map +1 -0
- package/dist/providers/typesafe.models.js +8 -0
- package/dist/providers/typesafe.models.js.map +1 -0
- package/dist/providers/vercel-ai-gateway.d.ts +3 -0
- package/dist/providers/vercel-ai-gateway.d.ts.map +1 -0
- package/dist/providers/vercel-ai-gateway.js +18 -0
- package/dist/providers/vercel-ai-gateway.js.map +1 -0
- package/dist/providers/vercel-ai-gateway.models.d.ts +6 -0
- package/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -0
- package/dist/providers/vercel-ai-gateway.models.js +8 -0
- package/dist/providers/vercel-ai-gateway.models.js.map +1 -0
- package/dist/providers/xai.d.ts +3 -0
- package/dist/providers/xai.d.ts.map +1 -0
- package/dist/providers/xai.js +24 -0
- package/dist/providers/xai.js.map +1 -0
- package/dist/providers/xai.models.d.ts +6 -0
- package/dist/providers/xai.models.d.ts.map +1 -0
- package/dist/providers/xai.models.js +8 -0
- package/dist/providers/xai.models.js.map +1 -0
- package/dist/providers/xiaomi-token-plan-ams.d.ts +3 -0
- package/dist/providers/xiaomi-token-plan-ams.d.ts.map +1 -0
- package/dist/providers/xiaomi-token-plan-ams.js +15 -0
- package/dist/providers/xiaomi-token-plan-ams.js.map +1 -0
- package/dist/providers/xiaomi-token-plan-ams.models.d.ts +6 -0
- package/dist/providers/xiaomi-token-plan-ams.models.d.ts.map +1 -0
- package/dist/providers/xiaomi-token-plan-ams.models.js +8 -0
- package/dist/providers/xiaomi-token-plan-ams.models.js.map +1 -0
- package/dist/providers/xiaomi-token-plan-cn.d.ts +3 -0
- package/dist/providers/xiaomi-token-plan-cn.d.ts.map +1 -0
- package/dist/providers/xiaomi-token-plan-cn.js +15 -0
- package/dist/providers/xiaomi-token-plan-cn.js.map +1 -0
- package/dist/providers/xiaomi-token-plan-cn.models.d.ts +6 -0
- package/dist/providers/xiaomi-token-plan-cn.models.d.ts.map +1 -0
- package/dist/providers/xiaomi-token-plan-cn.models.js +8 -0
- package/dist/providers/xiaomi-token-plan-cn.models.js.map +1 -0
- package/dist/providers/xiaomi-token-plan-sgp.d.ts +3 -0
- package/dist/providers/xiaomi-token-plan-sgp.d.ts.map +1 -0
- package/dist/providers/xiaomi-token-plan-sgp.js +15 -0
- package/dist/providers/xiaomi-token-plan-sgp.js.map +1 -0
- package/dist/providers/xiaomi-token-plan-sgp.models.d.ts +6 -0
- package/dist/providers/xiaomi-token-plan-sgp.models.d.ts.map +1 -0
- package/dist/providers/xiaomi-token-plan-sgp.models.js +8 -0
- package/dist/providers/xiaomi-token-plan-sgp.models.js.map +1 -0
- package/dist/providers/xiaomi.d.ts +3 -0
- package/dist/providers/xiaomi.d.ts.map +1 -0
- package/dist/providers/xiaomi.js +15 -0
- package/dist/providers/xiaomi.js.map +1 -0
- package/dist/providers/xiaomi.models.d.ts +6 -0
- package/dist/providers/xiaomi.models.d.ts.map +1 -0
- package/dist/providers/xiaomi.models.js +8 -0
- package/dist/providers/xiaomi.models.js.map +1 -0
- package/dist/providers/zai-coding-cn.d.ts +3 -0
- package/dist/providers/zai-coding-cn.d.ts.map +1 -0
- package/dist/providers/zai-coding-cn.js +15 -0
- package/dist/providers/zai-coding-cn.js.map +1 -0
- package/dist/providers/zai-coding-cn.models.d.ts +6 -0
- package/dist/providers/zai-coding-cn.models.d.ts.map +1 -0
- package/dist/providers/zai-coding-cn.models.js +8 -0
- package/dist/providers/zai-coding-cn.models.js.map +1 -0
- package/dist/providers/zai.d.ts +3 -0
- package/dist/providers/zai.d.ts.map +1 -0
- package/dist/providers/zai.js +15 -0
- package/dist/providers/zai.js.map +1 -0
- package/dist/providers/zai.models.d.ts +6 -0
- package/dist/providers/zai.models.d.ts.map +1 -0
- package/dist/providers/zai.models.js +8 -0
- package/dist/providers/zai.models.js.map +1 -0
- package/dist/session-resources.d.ts +4 -0
- package/dist/session-resources.d.ts.map +1 -0
- package/dist/session-resources.js +22 -0
- package/dist/session-resources.js.map +1 -0
- package/dist/types.d.ts +982 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/abort-signals.d.ts +6 -0
- package/dist/utils/abort-signals.d.ts.map +1 -0
- package/dist/utils/abort-signals.js +34 -0
- package/dist/utils/abort-signals.js.map +1 -0
- package/dist/utils/abort.d.ts +8 -0
- package/dist/utils/abort.d.ts.map +1 -0
- package/dist/utils/abort.js +49 -0
- package/dist/utils/abort.js.map +1 -0
- package/dist/utils/assistant-message-frame.d.ts +82 -0
- package/dist/utils/assistant-message-frame.d.ts.map +1 -0
- package/dist/utils/assistant-message-frame.js +430 -0
- package/dist/utils/assistant-message-frame.js.map +1 -0
- package/dist/utils/diagnostics.d.ts +20 -0
- package/dist/utils/diagnostics.d.ts.map +1 -0
- package/dist/utils/diagnostics.js +25 -0
- package/dist/utils/diagnostics.js.map +1 -0
- package/dist/utils/error-body.d.ts +25 -0
- package/dist/utils/error-body.d.ts.map +1 -0
- package/dist/utils/error-body.js +133 -0
- package/dist/utils/error-body.js.map +1 -0
- package/dist/utils/estimate.d.ts +17 -0
- package/dist/utils/estimate.d.ts.map +1 -0
- package/dist/utils/estimate.js +95 -0
- package/dist/utils/estimate.js.map +1 -0
- package/dist/utils/event-stream.d.ts +21 -0
- package/dist/utils/event-stream.d.ts.map +1 -0
- package/dist/utils/event-stream.js +99 -0
- package/dist/utils/event-stream.js.map +1 -0
- package/dist/utils/hash.d.ts +3 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +14 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/headers.d.ts +4 -0
- package/dist/utils/headers.d.ts.map +1 -0
- package/dist/utils/headers.js +20 -0
- package/dist/utils/headers.js.map +1 -0
- package/dist/utils/json-parse.d.ts +16 -0
- package/dist/utils/json-parse.d.ts.map +1 -0
- package/dist/utils/json-parse.js +113 -0
- package/dist/utils/json-parse.js.map +1 -0
- package/dist/utils/model-operations.d.ts +11 -0
- package/dist/utils/model-operations.d.ts.map +1 -0
- package/dist/utils/model-operations.js +47 -0
- package/dist/utils/model-operations.js.map +1 -0
- package/dist/utils/models-error.d.ts +8 -0
- package/dist/utils/models-error.d.ts.map +1 -0
- package/dist/utils/models-error.js +19 -0
- package/dist/utils/models-error.js.map +1 -0
- package/dist/utils/node-http-proxy.d.ts +4 -0
- package/dist/utils/node-http-proxy.d.ts.map +1 -0
- package/dist/utils/node-http-proxy.js +133 -0
- package/dist/utils/node-http-proxy.js.map +1 -0
- package/dist/utils/oauth-page.d.ts +3 -0
- package/dist/utils/oauth-page.d.ts.map +1 -0
- package/dist/utils/oauth-page.js +105 -0
- package/dist/utils/oauth-page.js.map +1 -0
- package/dist/utils/overflow.d.ts +69 -0
- package/dist/utils/overflow.d.ts.map +1 -0
- package/dist/utils/overflow.js +179 -0
- package/dist/utils/overflow.js.map +1 -0
- package/dist/utils/pi-user-agent.d.ts +2 -0
- package/dist/utils/pi-user-agent.d.ts.map +1 -0
- package/dist/utils/pi-user-agent.js +12 -0
- package/dist/utils/pi-user-agent.js.map +1 -0
- package/dist/utils/provider-env.d.ts +7 -0
- package/dist/utils/provider-env.d.ts.map +1 -0
- package/dist/utils/provider-env.js +44 -0
- package/dist/utils/provider-env.js.map +1 -0
- package/dist/utils/provider-retry.d.ts +16 -0
- package/dist/utils/provider-retry.d.ts.map +1 -0
- package/dist/utils/provider-retry.js +95 -0
- package/dist/utils/provider-retry.js.map +1 -0
- package/dist/utils/retry.d.ts +58 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +190 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/sanitize-unicode.d.ts +22 -0
- package/dist/utils/sanitize-unicode.d.ts.map +1 -0
- package/dist/utils/sanitize-unicode.js +26 -0
- package/dist/utils/sanitize-unicode.js.map +1 -0
- package/dist/utils/sleep.d.ts +2 -0
- package/dist/utils/sleep.d.ts.map +1 -0
- package/dist/utils/sleep.js +15 -0
- package/dist/utils/sleep.js.map +1 -0
- package/dist/utils/text.d.ts +14 -0
- package/dist/utils/text.d.ts.map +1 -0
- package/dist/utils/text.js +36 -0
- package/dist/utils/text.js.map +1 -0
- package/dist/utils/transcript.d.ts +84 -0
- package/dist/utils/transcript.d.ts.map +1 -0
- package/dist/utils/transcript.js +204 -0
- package/dist/utils/transcript.js.map +1 -0
- package/dist/utils/typebox-helpers.d.ts +17 -0
- package/dist/utils/typebox-helpers.d.ts.map +1 -0
- package/dist/utils/typebox-helpers.js +21 -0
- package/dist/utils/typebox-helpers.js.map +1 -0
- package/dist/utils/uuid.d.ts +3 -0
- package/dist/utils/uuid.d.ts.map +1 -0
- package/dist/utils/uuid.js +42 -0
- package/dist/utils/uuid.js.map +1 -0
- package/dist/utils/validation.d.ts +18 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +309 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +96 -0
package/README.md
ADDED
|
@@ -0,0 +1,1938 @@
|
|
|
1
|
+
# @panticonic/pi-ai
|
|
2
|
+
|
|
3
|
+
Unified LLM API with provider collections, automatic auth resolution, token and cost tracking, and simple context persistence and hand-off to other models mid-session.
|
|
4
|
+
|
|
5
|
+
**Note**: The chat catalog only includes models that support tool calling (function calling), as this is essential for agentic workflows. Image and classifier catalogs use their operation-specific capabilities.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Supported Providers](#supported-providers)
|
|
10
|
+
- [Installation](#installation)
|
|
11
|
+
- [Quick Start](#quick-start)
|
|
12
|
+
- [Providers and Models](#providers-and-models)
|
|
13
|
+
- [Provider Factories](#provider-factories)
|
|
14
|
+
- [All Built-in Providers](#all-built-in-providers)
|
|
15
|
+
- [Querying Models](#querying-models)
|
|
16
|
+
- [Static Catalog Reads](#static-catalog-reads)
|
|
17
|
+
- [Dynamic Providers](#dynamic-providers)
|
|
18
|
+
- [Auth](#auth)
|
|
19
|
+
- [How Auth Resolves](#how-auth-resolves)
|
|
20
|
+
- [Transforming Request Headers](#transforming-request-headers)
|
|
21
|
+
- [Credential Store](#credential-store)
|
|
22
|
+
- [Environment Variables](#environment-variables)
|
|
23
|
+
- [Tools](#tools)
|
|
24
|
+
- [Defining Tools](#defining-tools)
|
|
25
|
+
- [Handling Tool Calls](#handling-tool-calls)
|
|
26
|
+
- [Streaming Tool Calls with Partial JSON](#streaming-tool-calls-with-partial-json)
|
|
27
|
+
- [Validating Tool Arguments](#validating-tool-arguments)
|
|
28
|
+
- [Complete Event Reference](#complete-event-reference)
|
|
29
|
+
- [Compact Assistant Message Frames](#compact-assistant-message-frames)
|
|
30
|
+
- [Image Input](#image-input)
|
|
31
|
+
- [Image Generation](#image-generation)
|
|
32
|
+
- [Classification](#classification)
|
|
33
|
+
- [Thinking/Reasoning](#thinkingreasoning)
|
|
34
|
+
- [Unified Interface](#unified-interface-streamsimplecompletesimple)
|
|
35
|
+
- [Provider-Specific Options](#provider-specific-options-streamcomplete)
|
|
36
|
+
- [Streaming Thinking Content](#streaming-thinking-content)
|
|
37
|
+
- [Stop Reasons](#stop-reasons)
|
|
38
|
+
- [Error Handling](#error-handling)
|
|
39
|
+
- [Aborting Requests](#aborting-requests)
|
|
40
|
+
- [Continuing After Abort](#continuing-after-abort)
|
|
41
|
+
- [Debugging Provider Payloads](#debugging-provider-payloads)
|
|
42
|
+
- [Observing Provider Stream Events](#observing-provider-stream-events)
|
|
43
|
+
- [Custom Providers](#custom-providers)
|
|
44
|
+
- [createProvider()](#createprovider)
|
|
45
|
+
- [Calling API Implementations Directly](#calling-api-implementations-directly)
|
|
46
|
+
- [OpenAI Compatibility Settings](#openai-compatibility-settings)
|
|
47
|
+
- [Faux Provider for Tests](#faux-provider-for-tests)
|
|
48
|
+
- [Cross-Provider Handoffs](#cross-provider-handoffs)
|
|
49
|
+
- [System Messages](#system-messages)
|
|
50
|
+
- [Context Serialization](#context-serialization)
|
|
51
|
+
- [Browser Usage](#browser-usage)
|
|
52
|
+
- [Bundling and Tree Shaking](#bundling-and-tree-shaking)
|
|
53
|
+
- [OAuth Providers](#oauth-providers)
|
|
54
|
+
- [Vertex AI](#vertex-ai)
|
|
55
|
+
- [CLI Login](#cli-login)
|
|
56
|
+
- [Programmatic OAuth](#programmatic-oauth)
|
|
57
|
+
- [Migrating from the Old Global API](#migrating-from-the-old-global-api)
|
|
58
|
+
- [Development](#development)
|
|
59
|
+
- [License](#license)
|
|
60
|
+
|
|
61
|
+
## Supported Providers
|
|
62
|
+
|
|
63
|
+
- **OpenAI**
|
|
64
|
+
- **Ant Ling**
|
|
65
|
+
- **Azure OpenAI (Responses)**
|
|
66
|
+
- **OpenAI Codex (legacy)** (ChatGPT Plus/Pro subscription, requires OAuth, see below)
|
|
67
|
+
- **Radius** (API key or OAuth, with a dynamically refreshed gateway catalog)
|
|
68
|
+
- **TypeSafe** (System One classifier API)
|
|
69
|
+
- **DeepSeek**
|
|
70
|
+
- **NVIDIA NIM**
|
|
71
|
+
- **Anthropic**
|
|
72
|
+
- **Google**
|
|
73
|
+
- **Vertex AI** (Gemini via Vertex AI)
|
|
74
|
+
- **Mistral**
|
|
75
|
+
- **Groq**
|
|
76
|
+
- **Cerebras**
|
|
77
|
+
- **Cloudflare AI Gateway**
|
|
78
|
+
- **Cloudflare Workers AI**
|
|
79
|
+
- **xAI**
|
|
80
|
+
- **OpenRouter**
|
|
81
|
+
- **Vercel AI Gateway**
|
|
82
|
+
- **ZAI Coding Plan (Global)** (with separate China provider)
|
|
83
|
+
- **MiniMax** (with separate China provider)
|
|
84
|
+
- **Together AI**
|
|
85
|
+
- **Baseten**
|
|
86
|
+
- **Hugging Face**
|
|
87
|
+
- **Moonshot AI** (with separate China provider)
|
|
88
|
+
- **GitHub Copilot** (requires OAuth, see below)
|
|
89
|
+
- **Amazon Bedrock**
|
|
90
|
+
- **OpenCode Zen**
|
|
91
|
+
- **OpenCode Go**
|
|
92
|
+
- **Fireworks** (uses OpenAI- and Anthropic-compatible APIs)
|
|
93
|
+
- **Kimi For Coding** (Moonshot AI subscription endpoint, uses Anthropic-compatible API)
|
|
94
|
+
- **Meta** (Model API, uses OpenAI Responses-compatible API)
|
|
95
|
+
- **Qwen Token Plan** (separate Individual and existing catalogs, with a separate China provider)
|
|
96
|
+
- **Xiaomi MiMo** (defaults to API billing endpoint, with separate Token Plan providers for `cn`/`ams`/`sgp` regions)
|
|
97
|
+
- **Any OpenAI-compatible API**: Ollama, vLLM, LM Studio, etc.
|
|
98
|
+
|
|
99
|
+
## Installation
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npm install @panticonic/pi-ai
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
TypeBox exports are re-exported from `@panticonic/pi-ai`: `Type`, `Static`, and `TSchema`.
|
|
106
|
+
|
|
107
|
+
## Quick Start
|
|
108
|
+
|
|
109
|
+
You build a `Models` collection of providers and stream through it. The quickest start registers every built-in provider; apps that care about bundle size register individual providers instead (see [Provider Factories](#provider-factories) and [Bundling and Tree Shaking](#bundling-and-tree-shaking)).
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { Type, type Context, type Tool } from '@panticonic/pi-ai';
|
|
113
|
+
import { builtinModels } from '@panticonic/pi-ai/providers/all';
|
|
114
|
+
|
|
115
|
+
// A Models collection with every built-in provider registered
|
|
116
|
+
const models = builtinModels();
|
|
117
|
+
|
|
118
|
+
// Sync lookup against the collection
|
|
119
|
+
const model = models.getModel('openai', 'gpt-4o-mini')!;
|
|
120
|
+
|
|
121
|
+
// Define tools with TypeBox schemas for type safety and validation
|
|
122
|
+
const tools: Tool[] = [{
|
|
123
|
+
name: 'get_time',
|
|
124
|
+
description: 'Get the current time',
|
|
125
|
+
parameters: Type.Object({
|
|
126
|
+
timezone: Type.Optional(Type.String({ description: 'Optional timezone (e.g., America/New_York)' }))
|
|
127
|
+
})
|
|
128
|
+
}];
|
|
129
|
+
|
|
130
|
+
// Build a conversation context (easily serializable and transferable between models)
|
|
131
|
+
const context: Context = {
|
|
132
|
+
systemPrompt: 'You are a helpful assistant.',
|
|
133
|
+
messages: [{ role: 'user', content: 'What time is it?', timestamp: Date.now() }],
|
|
134
|
+
tools
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Option 1: Streaming with all event types.
|
|
138
|
+
// Auth resolves through the provider (OPENAI_API_KEY from the environment here).
|
|
139
|
+
const s = models.stream(model, context);
|
|
140
|
+
|
|
141
|
+
for await (const event of s) {
|
|
142
|
+
switch (event.type) {
|
|
143
|
+
case 'start':
|
|
144
|
+
console.log(`Starting with ${event.partial.model}`);
|
|
145
|
+
break;
|
|
146
|
+
case 'text_start':
|
|
147
|
+
console.log('\n[Text started]');
|
|
148
|
+
break;
|
|
149
|
+
case 'text_delta':
|
|
150
|
+
process.stdout.write(event.delta);
|
|
151
|
+
break;
|
|
152
|
+
case 'text_end':
|
|
153
|
+
console.log('\n[Text ended]');
|
|
154
|
+
break;
|
|
155
|
+
case 'thinking_start':
|
|
156
|
+
console.log('[Model is thinking...]');
|
|
157
|
+
break;
|
|
158
|
+
case 'thinking_delta':
|
|
159
|
+
process.stdout.write(event.delta);
|
|
160
|
+
break;
|
|
161
|
+
case 'thinking_end':
|
|
162
|
+
console.log('[Thinking complete]');
|
|
163
|
+
break;
|
|
164
|
+
case 'toolcall_start':
|
|
165
|
+
console.log(`\n[Tool call started: index ${event.contentIndex}]`);
|
|
166
|
+
break;
|
|
167
|
+
case 'toolcall_delta':
|
|
168
|
+
// Partial tool arguments are being streamed
|
|
169
|
+
const partialCall = event.partial.content[event.contentIndex];
|
|
170
|
+
if (partialCall.type === 'toolCall') {
|
|
171
|
+
console.log(`[Streaming args for ${partialCall.name}]`);
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
case 'toolcall_end':
|
|
175
|
+
console.log(`\nTool called: ${event.toolCall.name}`);
|
|
176
|
+
console.log(`Arguments: ${JSON.stringify(event.toolCall.arguments)}`);
|
|
177
|
+
break;
|
|
178
|
+
case 'done':
|
|
179
|
+
console.log(`\nFinished: ${event.reason}`);
|
|
180
|
+
break;
|
|
181
|
+
case 'error':
|
|
182
|
+
console.error(`Error: ${event.error.errorMessage}`);
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Get the final message after streaming, add it to the context
|
|
188
|
+
const finalMessage = await s.result();
|
|
189
|
+
context.messages.push(finalMessage);
|
|
190
|
+
|
|
191
|
+
// Handle tool calls if any
|
|
192
|
+
const toolCalls = finalMessage.content.filter(b => b.type === 'toolCall');
|
|
193
|
+
for (const call of toolCalls) {
|
|
194
|
+
const result = call.name === 'get_time'
|
|
195
|
+
? new Date().toLocaleString('en-US', {
|
|
196
|
+
timeZone: call.arguments.timezone || 'UTC',
|
|
197
|
+
dateStyle: 'full',
|
|
198
|
+
timeStyle: 'long'
|
|
199
|
+
})
|
|
200
|
+
: 'Unknown tool';
|
|
201
|
+
|
|
202
|
+
// Add tool result to context (supports text and images)
|
|
203
|
+
context.messages.push({
|
|
204
|
+
role: 'toolResult',
|
|
205
|
+
toolCallId: call.id,
|
|
206
|
+
toolName: call.name,
|
|
207
|
+
content: [{ type: 'text', text: result }],
|
|
208
|
+
isError: false,
|
|
209
|
+
timestamp: Date.now()
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Continue if there were tool calls
|
|
214
|
+
if (toolCalls.length > 0) {
|
|
215
|
+
const continuation = await models.complete(model, context);
|
|
216
|
+
context.messages.push(continuation);
|
|
217
|
+
console.log('After tool execution:', continuation.content);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
console.log(`Total tokens: ${finalMessage.usage.input} in, ${finalMessage.usage.output} out`);
|
|
221
|
+
console.log(`Cost: $${finalMessage.usage.cost.total.toFixed(4)}`);
|
|
222
|
+
|
|
223
|
+
// Option 2: Get complete response without streaming
|
|
224
|
+
const response = await models.complete(model, context);
|
|
225
|
+
|
|
226
|
+
for (const block of response.content) {
|
|
227
|
+
if (block.type === 'text') {
|
|
228
|
+
console.log(block.text);
|
|
229
|
+
} else if (block.type === 'toolCall') {
|
|
230
|
+
console.log(`Tool: ${block.name}(${JSON.stringify(block.arguments)})`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Snippets in the rest of this README assume a `models` collection set up like this (with the relevant providers registered).
|
|
236
|
+
|
|
237
|
+
## Providers and Models
|
|
238
|
+
|
|
239
|
+
A **provider** is the runtime unit: it owns its model catalog, its auth (API key resolution, OAuth flows), and its stream behavior. A `Models` collection holds providers and routes every request to the provider that owns the model.
|
|
240
|
+
|
|
241
|
+
Providers internally share **API implementations** (the wire protocols): Anthropic models use `anthropic-messages`, OpenAI uses `openai-responses`, while xAI, Groq, Cerebras, OpenRouter, and most others share `openai-completions`. Mixed-API providers (GitHub Copilot, OpenCode Zen) dispatch per model.
|
|
242
|
+
|
|
243
|
+
### Provider Factories
|
|
244
|
+
|
|
245
|
+
For apps that only need specific providers, there is one factory per built-in provider, each a subpath import that pulls only that provider's catalog:
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { anthropicProvider } from '@panticonic/pi-ai/providers/anthropic';
|
|
249
|
+
import { openaiProvider } from '@panticonic/pi-ai/providers/openai';
|
|
250
|
+
import { openrouterProvider } from '@panticonic/pi-ai/providers/openrouter';
|
|
251
|
+
import { amazonBedrockProvider } from '@panticonic/pi-ai/providers/amazon-bedrock';
|
|
252
|
+
// ...one module per provider in the Supported Providers list
|
|
253
|
+
|
|
254
|
+
const models = createModels();
|
|
255
|
+
models.setProvider(anthropicProvider());
|
|
256
|
+
models.setProvider(openrouterProvider());
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Provider factories import their model catalog and a lazy API wrapper. They do not import other providers. With bundler code splitting, SDK implementations (`@anthropic-ai/sdk`, `openai`, `@google/genai`, etc.) stay in lazy chunks loaded on the first request to a model of that API.
|
|
260
|
+
|
|
261
|
+
### All Built-in Providers
|
|
262
|
+
|
|
263
|
+
For apps that want everything (as in Quick Start):
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
import { builtinModels } from '@panticonic/pi-ai/providers/all';
|
|
267
|
+
|
|
268
|
+
const models = builtinModels(); // a Models collection with every built-in provider registered
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
This imports all catalogs and every built-in provider factory. It is the heavy, explicit entrypoint. `builtinModels()` accepts the same options as `createModels()` (`credentials`, `authContext`); `builtinProviders()` returns the provider array if you want to register them on your own collection.
|
|
272
|
+
|
|
273
|
+
### Querying Models
|
|
274
|
+
|
|
275
|
+
Reads are synchronous and return the last-known lists:
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
const providers = models.getProviders(); // registered Provider objects
|
|
279
|
+
const provider = models.getProvider('anthropic'); // one provider
|
|
280
|
+
|
|
281
|
+
const all = models.getModels(); // every chat model across providers
|
|
282
|
+
const anthropicModels = models.getModels('anthropic');
|
|
283
|
+
const model = models.getModel('anthropic', 'claude-sonnet-4-5');
|
|
284
|
+
|
|
285
|
+
for (const m of anthropicModels) {
|
|
286
|
+
console.log(`${m.id}: ${m.name}`);
|
|
287
|
+
console.log(` API: ${m.api}`);
|
|
288
|
+
console.log(` Context: ${m.contextWindow} tokens`);
|
|
289
|
+
console.log(` Vision: ${m.input.includes('image')}`);
|
|
290
|
+
console.log(` Reasoning: ${m.reasoning}`);
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
The unqualified reads `getModels()`/`getModel()`/`getAvailable()` return chat models (`Model<Api>`) usable with `stream()`. The `*OfType` reads return one model type, and `getAllModels()`/`getAllAvailable()` return every type as `AnyModel`:
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
const images = models.getModelsOfType('image', 'openrouter'); // ImageModel[]
|
|
298
|
+
const flux = models.getModelOfType('image', 'openrouter', 'black-forest-labs/flux.2-pro');
|
|
299
|
+
const jev = models.getModelOfType('classifier', 'typesafe', 'jev-latest');
|
|
300
|
+
const availableImages = await models.getAvailableOfType('image');
|
|
301
|
+
const everything = models.getAllModels(); // AnyModel[]
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
The model's `type` decides which operation accepts it: chat models stream, `type: "image"` models generate images, and `type: "classifier"` models classify structured state. `type` is optional on chat models, so a model without `type` is a chat model. Do not compare `type` directly; narrow mixed lists with `isModelType()` or read the effective type with `getModelType()`:
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
import { isModelType } from '@panticonic/pi-ai';
|
|
308
|
+
|
|
309
|
+
for (const model of models.getAllModels()) {
|
|
310
|
+
if (isModelType(model, 'image')) {
|
|
311
|
+
// model: ImageModel<ImageApi>
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
IDs are unique within each provider and type; one upstream model may have separate entries for different operations. On a provider, `getModels()` returns chat models and the optional `getAllModels()` returns every type; providers with only chat models can omit it.
|
|
317
|
+
|
|
318
|
+
Dynamically listed chat models are typed `Model<Api>`. Narrow with the `hasApi()` guard when you need API-specific option typing:
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
import { hasApi } from '@panticonic/pi-ai';
|
|
322
|
+
|
|
323
|
+
const m = models.getModel('anthropic', 'claude-sonnet-4-5');
|
|
324
|
+
if (m && hasApi(m, 'anthropic-messages')) {
|
|
325
|
+
// m: Model<'anthropic-messages'> — stream options fully typed
|
|
326
|
+
models.stream(m, context, { thinkingEnabled: true, thinkingBudgetTokens: 2048 });
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Static Catalog Reads
|
|
331
|
+
|
|
332
|
+
For tooling that wants the generated built-in catalog with full literal typing (provider and model IDs auto-complete), independent of any collection:
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
import {
|
|
336
|
+
getAllBuiltinModels,
|
|
337
|
+
getBuiltinClassifierModel,
|
|
338
|
+
getBuiltinClassifierModels,
|
|
339
|
+
getBuiltinImageModel,
|
|
340
|
+
getBuiltinImageModels,
|
|
341
|
+
getBuiltinModel,
|
|
342
|
+
getBuiltinModels,
|
|
343
|
+
getBuiltinProviders,
|
|
344
|
+
} from '@panticonic/pi-ai/providers/all';
|
|
345
|
+
|
|
346
|
+
const model = getBuiltinModel('openai', 'gpt-4o-mini'); // typed Model<'openai-responses'>
|
|
347
|
+
const radius = getBuiltinModel('radius', 'balanced'); // typed Model<'pi-messages'>
|
|
348
|
+
const flux = getBuiltinImageModel('openrouter', 'black-forest-labs/flux.2-pro');
|
|
349
|
+
const jev = getBuiltinClassifierModel('typesafe', 'jev-latest');
|
|
350
|
+
const providers = getBuiltinProviders();
|
|
351
|
+
const openrouterChat = getBuiltinModels('openrouter'); // Model[]
|
|
352
|
+
const openrouterImages = getBuiltinImageModels('openrouter'); // ImageModel[]
|
|
353
|
+
const typesafeClassifiers = getBuiltinClassifierModels('typesafe'); // ClassifierModel[]
|
|
354
|
+
const openrouterAll = getAllBuiltinModels('openrouter'); // AnyModel[]
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Dynamic Providers
|
|
358
|
+
|
|
359
|
+
Providers may have dynamic model lists (a llama.cpp server, a live OpenRouter listing). Reads stay sync; fetching is an explicit async verb:
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
// getModels() returns the last-known list (empty before the first refresh)
|
|
363
|
+
await models.refresh({ providers: ['llamacpp'] }); // refresh one provider
|
|
364
|
+
await models.refresh(); // refresh all providers concurrently, best-effort
|
|
365
|
+
const fresh = models.getModel('llamacpp', 'qwen3-30b');
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Static built-in providers are no-ops for `refresh()`. Radius is both static and dynamic: it ships the public `radius.pi.dev` catalog for synchronous API lookup, then overlays cached and freshly fetched `/v1/config` models when refreshed with configured auth. See [createProvider()](#createprovider) for building a dynamic provider.
|
|
369
|
+
|
|
370
|
+
## Auth
|
|
371
|
+
|
|
372
|
+
Every provider owns its auth: how API keys resolve (stored credentials, environment variables, ambient sources like AWS profiles or gcloud ADC) and, where supported, OAuth login/refresh flows.
|
|
373
|
+
|
|
374
|
+
### How Auth Resolves
|
|
375
|
+
|
|
376
|
+
When you call `models.stream()`, the collection resolves auth through the owning provider and merges it into the request. Explicit per-request values always win:
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
// Resolved through the provider (env var, stored credential, OAuth token):
|
|
380
|
+
await models.complete(model, context);
|
|
381
|
+
|
|
382
|
+
// Explicit key wins over anything the provider would resolve:
|
|
383
|
+
await models.complete(model, context, { apiKey: 'sk-explicit' });
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
You can inspect resolution without making a request. Pass a provider ID for provider-scoped auth, or a model to include its static `model.headers`:
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
const providerAuth = await models.getAuth(model.provider);
|
|
390
|
+
const modelAuth = await models.getAuth(model);
|
|
391
|
+
|
|
392
|
+
if (modelAuth) {
|
|
393
|
+
console.log(`configured via ${modelAuth.source}`); // e.g. "ANTHROPIC_API_KEY", "OAuth", "stored credential"
|
|
394
|
+
console.log(modelAuth.auth.headers); // Provider auth headers + model.headers
|
|
395
|
+
} else {
|
|
396
|
+
console.log('not configured');
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Both overloads resolve credentials, refresh expired OAuth when necessary, and may return an auth-derived `apiKey`, `headers`, or `baseUrl`. `getAuth()` resolves `undefined` for unconfigured providers and rejects with `ModelsError` when something is actually broken (`"oauth"`: token refresh failed, credential preserved for re-login; `"auth"`: key resolution or credential store failure). Request paths surface the same failures as stream errors.
|
|
401
|
+
|
|
402
|
+
`getAuth()`, `checkAuth()`, `getAvailable()`, login, and logout accept optional caller cancellation through their existing options or interaction objects and remain unbounded when no signal is supplied. Provider `login`, `ApiKeyAuth.check`, `ApiKeyAuth.resolve`, and `OAuthAuth.refresh` implementations always receive a concrete signal and must honor it for blocking work.
|
|
403
|
+
|
|
404
|
+
### Transforming Request Headers
|
|
405
|
+
|
|
406
|
+
`Models.stream()`, `complete()`, `streamSimple()`, and `completeSimple()` accept a Models-only `transformHeaders` option. It runs once after provider auth, `model.headers`, and explicit `options.headers` have been merged, but before provider dispatch:
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
const response = await models.completeSimple(model, context, {
|
|
410
|
+
headers: { "X-Client": "my-app" },
|
|
411
|
+
transformHeaders: async (headers) => ({
|
|
412
|
+
...headers,
|
|
413
|
+
"X-Request-ID": crypto.randomUUID(),
|
|
414
|
+
}),
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
The ordering is:
|
|
419
|
+
|
|
420
|
+
```text
|
|
421
|
+
provider auth headers -> model.headers -> explicit options.headers -> transformHeaders -> Provider.stream*()
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Header names are merged case-insensitively. Explicit headers override auth/model headers, and the transform has final control; returning `null` for a header suppresses lower-level defaults that support deletion.
|
|
425
|
+
|
|
426
|
+
`transformHeaders` belongs to `Models`, not `Provider`. A `Models` implementation must consume it and remove it before calling `Provider.stream*()`. Provider implementations continue receiving ordinary `ApiStreamOptions` or `SimpleStreamOptions` and never handle the transform themselves. Use this option instead of calling `getAuth(model)` before `stream*()`, which would resolve request auth twice.
|
|
427
|
+
|
|
428
|
+
### Credential Store
|
|
429
|
+
|
|
430
|
+
Stored credentials (API keys entered interactively, OAuth tokens) live in a `CredentialStore` — one type-tagged credential per provider. pi-ai ships an in-memory default; apps inject persistent storage:
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
import { createModels, type CredentialStore } from '@panticonic/pi-ai';
|
|
434
|
+
|
|
435
|
+
const models = createModels({ credentials: myFileBackedStore });
|
|
436
|
+
// builtinModels() takes the same options:
|
|
437
|
+
// const models = builtinModels({ credentials: myFileBackedStore });
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
The contract is small: `read(providerId)`, `list()` for non-secret `{ providerId, type }` metadata, `modify(providerId, fn)` (the only write path — a serialized read-modify-write), and `delete(providerId)`. Each operation accepts optional cancellation options. Enumeration must not resolve secrets or execute configured key commands. OAuth token refresh runs inside `modify`, so concurrent requests and processes cannot double-refresh a rotated token. A stored credential *owns* its provider: environment variables are only consulted when nothing is stored, and a failed refresh never silently falls back to an env key.
|
|
441
|
+
|
|
442
|
+
API-key credentials use the same discriminator as pi's `auth.json` and can carry provider-scoped env/config values:
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
const credential = {
|
|
446
|
+
type: 'api_key',
|
|
447
|
+
key: '...',
|
|
448
|
+
env: {
|
|
449
|
+
CLOUDFLARE_ACCOUNT_ID: 'account-id',
|
|
450
|
+
CLOUDFLARE_GATEWAY_ID: 'gateway-id'
|
|
451
|
+
}
|
|
452
|
+
} as const;
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Environment Variables
|
|
456
|
+
|
|
457
|
+
Built-in providers resolve these env vars (Node.js; in browsers pass `apiKey` explicitly):
|
|
458
|
+
|
|
459
|
+
| Provider | Environment Variable(s) |
|
|
460
|
+
|----------|------------------------|
|
|
461
|
+
| OpenAI | `OPENAI_API_KEY` |
|
|
462
|
+
| Ant Ling | `ANT_LING_API_KEY` |
|
|
463
|
+
| Azure OpenAI | `AZURE_OPENAI_API_KEY` + `AZURE_OPENAI_BASE_URL` (e.g. `https://{resource}.ai.azure.com`) or `AZURE_OPENAI_RESOURCE_NAME`. Supports `*.openai.azure.com`, `*.cognitiveservices.azure.com` and `*.ai.azure.com`; root endpoints auto-normalize to `/openai/v1`. Optional: `AZURE_OPENAI_API_VERSION` (default `v1`), `AZURE_OPENAI_DEPLOYMENT_NAME_MAP`. |
|
|
464
|
+
| Anthropic | `ANTHROPIC_API_KEY` or `ANTHROPIC_OAUTH_TOKEN` |
|
|
465
|
+
| Radius | `RADIUS_API_KEY` |
|
|
466
|
+
| TypeSafe | `TYPESAFE_API_KEY` |
|
|
467
|
+
| DeepSeek | `DEEPSEEK_API_KEY` |
|
|
468
|
+
| NVIDIA NIM | `NVIDIA_API_KEY` |
|
|
469
|
+
| Google | `GEMINI_API_KEY` |
|
|
470
|
+
| Vertex AI | `GOOGLE_CLOUD_API_KEY` or `GOOGLE_CLOUD_PROJECT` (or `GCLOUD_PROJECT`) + `GOOGLE_CLOUD_LOCATION` + ADC |
|
|
471
|
+
| Mistral | `MISTRAL_API_KEY` |
|
|
472
|
+
| Groq | `GROQ_API_KEY` |
|
|
473
|
+
| Cerebras | `CEREBRAS_API_KEY` |
|
|
474
|
+
| Cloudflare AI Gateway | `CLOUDFLARE_API_KEY` + `CLOUDFLARE_ACCOUNT_ID` + `CLOUDFLARE_GATEWAY_ID` |
|
|
475
|
+
| Cloudflare Workers AI | `CLOUDFLARE_API_KEY` + `CLOUDFLARE_ACCOUNT_ID` |
|
|
476
|
+
| xAI | `XAI_API_KEY` |
|
|
477
|
+
| Fireworks | `FIREWORKS_API_KEY` |
|
|
478
|
+
| Together AI | `TOGETHER_API_KEY` |
|
|
479
|
+
| Baseten | `BASETEN_API_KEY` |
|
|
480
|
+
| OpenRouter | `OPENROUTER_API_KEY` |
|
|
481
|
+
| Vercel AI Gateway | `AI_GATEWAY_API_KEY` |
|
|
482
|
+
| ZAI Coding Plan (Global) | `ZAI_API_KEY` |
|
|
483
|
+
| ZAI Coding Plan (China) | `ZAI_CODING_CN_API_KEY` |
|
|
484
|
+
| MiniMax (Global) | `MINIMAX_API_KEY` |
|
|
485
|
+
| MiniMax (China) | `MINIMAX_CN_API_KEY` |
|
|
486
|
+
| Moonshot AI / Moonshot AI (China) | `MOONSHOT_API_KEY` |
|
|
487
|
+
| Hugging Face | `HF_TOKEN` |
|
|
488
|
+
| OpenCode Zen / OpenCode Go | `OPENCODE_API_KEY` |
|
|
489
|
+
| Kimi For Coding | `KIMI_API_KEY` |
|
|
490
|
+
| Meta | `META_API_KEY` |
|
|
491
|
+
| Qwen Token Plan (existing catalog) | `QWEN_TOKEN_PLAN_API_KEY` |
|
|
492
|
+
| Qwen Token Plan (Individual) | `QWEN_TOKEN_PLAN_API_KEY` |
|
|
493
|
+
| Qwen Token Plan (China) | `QWEN_TOKEN_PLAN_CN_API_KEY` |
|
|
494
|
+
| Xiaomi MiMo (API billing) | `XIAOMI_API_KEY` |
|
|
495
|
+
| Xiaomi MiMo Token Plan (China) | `XIAOMI_TOKEN_PLAN_CN_API_KEY` |
|
|
496
|
+
| Xiaomi MiMo Token Plan (Amsterdam) | `XIAOMI_TOKEN_PLAN_AMS_API_KEY` |
|
|
497
|
+
| Xiaomi MiMo Token Plan (Singapore) | `XIAOMI_TOKEN_PLAN_SGP_API_KEY` |
|
|
498
|
+
| GitHub Copilot | `COPILOT_GITHUB_TOKEN` |
|
|
499
|
+
|
|
500
|
+
`qwen-token-plan-individual` and `qwen-token-plan` share the international endpoint and
|
|
501
|
+
`QWEN_TOKEN_PLAN_API_KEY`. The Individual provider exposes only the models documented for Individual
|
|
502
|
+
subscriptions, while the existing provider retains its broader catalog for backward compatibility.
|
|
503
|
+
Stored credentials remain provider-scoped, so save the key under the provider ID you register.
|
|
504
|
+
|
|
505
|
+
Amazon Bedrock resolves ambient AWS credentials (`AWS_PROFILE`, access key pairs, `AWS_BEARER_TOKEN_BEDROCK`, ECS task roles, web identity tokens); its provider-owned login flow supports bearer tokens, AWS profiles, and the existing credential chain. Vertex AI resolves either an explicit key or gcloud Application Default Credentials plus project/location, with a provider-owned login flow for API keys, ADC, and service-account files.
|
|
506
|
+
|
|
507
|
+
## Tools
|
|
508
|
+
|
|
509
|
+
Tools enable LLMs to interact with external systems. This library uses TypeBox schemas for type-safe tool definitions with automatic validation using TypeBox's built-in validator and value conversion utilities. TypeBox schemas can be serialized and deserialized as plain JSON, making them ideal for distributed systems.
|
|
510
|
+
|
|
511
|
+
### Defining Tools
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
import { Type, type Tool, StringEnum } from '@panticonic/pi-ai';
|
|
515
|
+
|
|
516
|
+
// Define tool parameters with TypeBox
|
|
517
|
+
const weatherTool: Tool = {
|
|
518
|
+
name: 'get_weather',
|
|
519
|
+
description: 'Get current weather for a location',
|
|
520
|
+
parameters: Type.Object({
|
|
521
|
+
location: Type.String({ description: 'City name or coordinates' }),
|
|
522
|
+
units: StringEnum(['celsius', 'fahrenheit'], { default: 'celsius' })
|
|
523
|
+
})
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
// Note: For Google API compatibility, use StringEnum helper instead of Type.Enum
|
|
527
|
+
// Type.Enum generates anyOf/const patterns that Google doesn't support
|
|
528
|
+
|
|
529
|
+
const bookMeetingTool: Tool = {
|
|
530
|
+
name: 'book_meeting',
|
|
531
|
+
description: 'Schedule a meeting',
|
|
532
|
+
parameters: Type.Object({
|
|
533
|
+
title: Type.String({ minLength: 1 }),
|
|
534
|
+
startTime: Type.String({ format: 'date-time' }),
|
|
535
|
+
endTime: Type.String({ format: 'date-time' }),
|
|
536
|
+
attendees: Type.Array(Type.String({ format: 'email' }), { minItems: 1 })
|
|
537
|
+
})
|
|
538
|
+
};
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### Constrained Sampling for Tools
|
|
542
|
+
|
|
543
|
+
Tools can opt in to provider-side constrained sampling. For JSON-schema tools, `strict: 'prefer'` uses provider-side strict schema enforcement when supported and otherwise falls back to normal tool calling. `strict: 'require'` fails the request when the active provider/model cannot honor it. Set `constrainedSampling: false` to explicitly opt out; it behaves the same as omitting the field.
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
const strictTool: Tool = {
|
|
547
|
+
name: 'edit_file',
|
|
548
|
+
description: 'Edit a file',
|
|
549
|
+
parameters: Type.Object({
|
|
550
|
+
path: Type.String(),
|
|
551
|
+
content: Type.String()
|
|
552
|
+
}, { additionalProperties: false }),
|
|
553
|
+
constrainedSampling: { type: 'json_schema', strict: 'prefer' }
|
|
554
|
+
};
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
Strict JSON-schema constrained sampling is supported for OpenAI, Anthropic, supported Amazon Bedrock Converse models, Mistral, and Gemini 3 tool calls through the Google Generative AI and Vertex adapters. Google uses `VALIDATED` function-calling mode (or `ANY` when explicitly requested); earlier Gemini versions fall back for `strict: 'prefer'` and reject `strict: 'require'` because they do not enforce required parameters. Bedrock strict-tool capability is generated from model structured-output metadata; custom Bedrock models can override `compat.supportsStrictMode`. OpenAI Responses and Chat Completions can also emit grammar-constrained custom tools with OpenAI Lark or regex grammar variants. If multiple OpenAI variants are supplied, Lark is preferred over regex. Grammar constraints are enforced when the active model supports grammar tools; otherwise the tool falls back to normal function/JSON-schema handling. Grammar tool capability is model metadata: the generated catalog sets `compat.supportsOpenAIGrammarTools` for GPT-5+ models on endpoints that pass OpenAI custom tools through (OpenAI, OpenAI Codex, Azure OpenAI Responses, GitHub Copilot, opencode, and Cloudflare AI Gateway). OpenAI rejects `type: "custom"` tools for pre-GPT-5 models, and gateways that normalize tool schemas (e.g. OpenRouter) mangle them, so the flag stays off elsewhere. Custom model definitions can opt in via `compat`. Grammar-capable models reject grammar configurations without a non-empty supported variant. Native grammar tools must have an object parameter schema with exactly one required string property:
|
|
558
|
+
|
|
559
|
+
```typescript
|
|
560
|
+
const patchTool: Tool = {
|
|
561
|
+
name: 'apply_patch',
|
|
562
|
+
description: 'Apply a patch',
|
|
563
|
+
parameters: Type.Object({
|
|
564
|
+
input: Type.String()
|
|
565
|
+
}, { additionalProperties: false }),
|
|
566
|
+
constrainedSampling: {
|
|
567
|
+
type: 'grammar',
|
|
568
|
+
variants: {
|
|
569
|
+
openai_lark: 'start: /.+/s'
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
### Handling Tool Calls
|
|
576
|
+
|
|
577
|
+
Tool results use content blocks and can include both text and images:
|
|
578
|
+
|
|
579
|
+
```typescript
|
|
580
|
+
import { readFileSync } from 'fs';
|
|
581
|
+
|
|
582
|
+
const context: Context = {
|
|
583
|
+
messages: [{ role: 'user', content: 'What is the weather in London?', timestamp: Date.now() }],
|
|
584
|
+
tools: [weatherTool]
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
const response = await models.complete(model, context);
|
|
588
|
+
|
|
589
|
+
// Check for tool calls in the response
|
|
590
|
+
for (const block of response.content) {
|
|
591
|
+
if (block.type === 'toolCall') {
|
|
592
|
+
// Execute your tool with the arguments
|
|
593
|
+
// See "Validating Tool Arguments" section for validation
|
|
594
|
+
const result = await executeWeatherApi(block.arguments);
|
|
595
|
+
|
|
596
|
+
// Add tool result with text content
|
|
597
|
+
context.messages.push({
|
|
598
|
+
role: 'toolResult',
|
|
599
|
+
toolCallId: block.id,
|
|
600
|
+
toolName: block.name,
|
|
601
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
602
|
+
isError: false,
|
|
603
|
+
timestamp: Date.now()
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Tool results can also include images (for vision-capable models)
|
|
609
|
+
const imageBuffer = readFileSync('chart.png');
|
|
610
|
+
context.messages.push({
|
|
611
|
+
role: 'toolResult',
|
|
612
|
+
toolCallId: 'tool_xyz',
|
|
613
|
+
toolName: 'generate_chart',
|
|
614
|
+
content: [
|
|
615
|
+
{ type: 'text', text: 'Generated chart showing temperature trends' },
|
|
616
|
+
{ type: 'image', data: imageBuffer.toString('base64'), mimeType: 'image/png' }
|
|
617
|
+
],
|
|
618
|
+
isError: false,
|
|
619
|
+
timestamp: Date.now()
|
|
620
|
+
});
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### Streaming Tool Calls with Partial JSON
|
|
624
|
+
|
|
625
|
+
During streaming, tool call arguments are progressively parsed as they arrive. This enables real-time UI updates before the complete arguments are available:
|
|
626
|
+
|
|
627
|
+
```typescript
|
|
628
|
+
const s = models.stream(model, context);
|
|
629
|
+
|
|
630
|
+
for await (const event of s) {
|
|
631
|
+
if (event.type === 'toolcall_delta') {
|
|
632
|
+
const toolCall = event.partial.content[event.contentIndex];
|
|
633
|
+
|
|
634
|
+
// toolCall.arguments contains partially parsed JSON during streaming
|
|
635
|
+
// This allows for progressive UI updates
|
|
636
|
+
if (toolCall.type === 'toolCall' && toolCall.arguments) {
|
|
637
|
+
// BE DEFENSIVE: arguments may be incomplete
|
|
638
|
+
// Example: Show file path being written even before content is complete
|
|
639
|
+
if (toolCall.name === 'write_file' && toolCall.arguments.path) {
|
|
640
|
+
console.log(`Writing to: ${toolCall.arguments.path}`);
|
|
641
|
+
|
|
642
|
+
// Content might be partial or missing
|
|
643
|
+
if (toolCall.arguments.content) {
|
|
644
|
+
console.log(`Content preview: ${toolCall.arguments.content.substring(0, 100)}...`);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (event.type === 'toolcall_end') {
|
|
651
|
+
// Here toolCall.arguments is complete (but not yet validated)
|
|
652
|
+
const toolCall = event.toolCall;
|
|
653
|
+
console.log(`Tool completed: ${toolCall.name}`, toolCall.arguments);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
**Important notes about partial tool arguments:**
|
|
659
|
+
- During `toolcall_delta` events, `arguments` contains the best-effort parse of partial JSON
|
|
660
|
+
- Fields may be missing or incomplete - always check for existence before use
|
|
661
|
+
- String values may be truncated mid-word
|
|
662
|
+
- Arrays may be incomplete
|
|
663
|
+
- Nested objects may be partially populated
|
|
664
|
+
- At minimum, `arguments` will be an empty object `{}`, never `undefined`
|
|
665
|
+
- The Google provider does not support function call streaming. Instead, you will receive a single `toolcall_delta` event with the full arguments.
|
|
666
|
+
|
|
667
|
+
### Validating Tool Arguments
|
|
668
|
+
|
|
669
|
+
When implementing your own tool execution loop, use `validateToolCall` to validate arguments before passing them to your tools:
|
|
670
|
+
|
|
671
|
+
```typescript
|
|
672
|
+
import { validateToolCall, type Tool } from '@panticonic/pi-ai';
|
|
673
|
+
|
|
674
|
+
const tools: Tool[] = [weatherTool, calculatorTool];
|
|
675
|
+
const s = models.stream(model, { messages, tools });
|
|
676
|
+
|
|
677
|
+
for await (const event of s) {
|
|
678
|
+
if (event.type === 'toolcall_end') {
|
|
679
|
+
const toolCall = event.toolCall;
|
|
680
|
+
|
|
681
|
+
try {
|
|
682
|
+
// Validate arguments against the tool's schema (throws on invalid args)
|
|
683
|
+
const validatedArgs = validateToolCall(tools, toolCall);
|
|
684
|
+
const result = await executeMyTool(toolCall.name, validatedArgs);
|
|
685
|
+
// ... add tool result to context
|
|
686
|
+
} catch (error) {
|
|
687
|
+
// Validation failed - return error as tool result so model can retry
|
|
688
|
+
context.messages.push({
|
|
689
|
+
role: 'toolResult',
|
|
690
|
+
toolCallId: toolCall.id,
|
|
691
|
+
toolName: toolCall.name,
|
|
692
|
+
content: [{ type: 'text', text: error.message }],
|
|
693
|
+
isError: true,
|
|
694
|
+
timestamp: Date.now()
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
### Complete Event Reference
|
|
702
|
+
|
|
703
|
+
Successful generation follows `start → updates* → done`. A failure after generation starts follows `start → updates* → error`. Request setup may fail before generation starts, in which case the stream contains only `error`; `done` and update events are invalid before `start`. Direct API `streamSimple()` calls throw synchronously when request auth is missing.
|
|
704
|
+
|
|
705
|
+
Every non-terminal event's `partial` is the shared live response-so-far helper. It is intentionally not an event-time snapshot: providers may mutate the same message and content blocks as generation advances, including while older events wait in the stream queue. Inspect it when handling an event instead of retaining it as historical state. Text and ordinary thinking blocks are empty when their `*_start` event is emitted and grow only through matching `*_delta` events until the authoritative `*_end`; redacted thinking may be complete at start and emit no deltas. Tool-call arguments at `toolcall_start` are provider-specific; `toolcall_delta` carries subsequent JSON updates.
|
|
706
|
+
|
|
707
|
+
All streaming events emitted during assistant message generation:
|
|
708
|
+
|
|
709
|
+
| Event Type | Description | Key Properties |
|
|
710
|
+
|------------|-------------|----------------|
|
|
711
|
+
| `start` | Stream begins | `partial`: Initial assistant message structure |
|
|
712
|
+
| `text_start` | Text block starts | `contentIndex`: Position in content array |
|
|
713
|
+
| `text_delta` | Text chunk received | `delta`: New text, `contentIndex`: Position |
|
|
714
|
+
| `text_end` | Text block complete | `content`: Full text, `contentIndex`: Position |
|
|
715
|
+
| `thinking_start` | Thinking block starts | `contentIndex`: Position in content array |
|
|
716
|
+
| `thinking_delta` | Thinking chunk received | `delta`: New text, `contentIndex`: Position |
|
|
717
|
+
| `thinking_end` | Thinking block complete | `content`: Full thinking, `contentIndex`: Position |
|
|
718
|
+
| `toolcall_start` | Tool call begins | `contentIndex`: Position in content array |
|
|
719
|
+
| `toolcall_delta` | Tool arguments streaming | `delta`: JSON chunk, `partial.content[contentIndex].arguments`: Partial parsed args |
|
|
720
|
+
| `toolcall_end` | Tool call complete | `toolCall`: Complete, but not schema-validated, tool call with `id`, `name`, `arguments` |
|
|
721
|
+
| `done` | Stream complete | `reason`: Stop reason ("stop", "length", "toolUse"), `message`: Final assistant message |
|
|
722
|
+
| `error` | Error occurred | `reason`: Error type ("error" or "aborted"), `error`: AssistantMessage with partial content |
|
|
723
|
+
|
|
724
|
+
Streaming events for different content blocks are not guaranteed to be contiguous. Providers may emit deltas for text, thinking, and tool calls in the same upstream chunk, and pi may surface corresponding events interleaved, for example `text_start`, `text_delta`, `toolcall_start`, `text_delta`, `toolcall_delta`. Consumers must use `contentIndex` to associate each delta/end event with its block and must not assume that a block's `*_start`/`*_delta`/`*_end` sequence is uninterrupted by events for other blocks.
|
|
725
|
+
|
|
726
|
+
### Compact Assistant Message Frames
|
|
727
|
+
|
|
728
|
+
`AssistantMessageFrameEncoder` converts one stream into compact, persistable `AssistantMessageFrame` values. Create one encoder per stream and feed it every event in order. The encoder understands that `partial` is live: a block-start event consumed after the provider has already queued later deltas snapshots the current block once, and covered queued text/thinking deltas produce no duplicate frame. It retains only per-open-block counters plus, temporarily, the raw prefix needed to synchronize an already-advanced tool call. It never clones the growing full partial per token.
|
|
729
|
+
|
|
730
|
+
The start frame contains message metadata with empty content. Text and thinking frames store each generated character at most once before the authoritative end frame. Tool calls that were already advanced when their start event was consumed use one compact JSON checkpoint before ordinary deltas resume. Terminal `done` and `error` events produce no frame because final message settlement is separate. A pre-generation `error` therefore produces no frames.
|
|
731
|
+
|
|
732
|
+
`reduceAssistantMessageFrames()` is the canonical pure reducer. It reconstructs text, thinking, and tool-call arguments, including interleaved blocks identified by `contentIndex`, and rejects malformed sequences. It performs a single pass over the iterable and returns `undefined` when there is no start frame. End frames replace blocks with the provider's authoritative completed content and metadata. The reducer does not validate tool arguments against a TypeBox schema; call `validateToolCall` before execution.
|
|
733
|
+
|
|
734
|
+
```typescript
|
|
735
|
+
import {
|
|
736
|
+
AssistantMessageFrameEncoder,
|
|
737
|
+
reduceAssistantMessageFrames,
|
|
738
|
+
type AssistantMessageFrame,
|
|
739
|
+
} from '@panticonic/pi-ai';
|
|
740
|
+
|
|
741
|
+
const encoder = new AssistantMessageFrameEncoder();
|
|
742
|
+
const frames: AssistantMessageFrame[] = [];
|
|
743
|
+
for await (const event of s) {
|
|
744
|
+
const frame = encoder.encode(event);
|
|
745
|
+
if (frame) frames.push(frame);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const reconstructedPartial = reduceAssistantMessageFrames(frames);
|
|
749
|
+
const finalMessage = await s.result(); // Persist terminal settlement separately.
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
An encoder rejects duplicate starts, updates before start, `done` before start, events after a terminal event, duplicate block starts, and block-kind mismatches. An `error` before start is valid and returns no frame.
|
|
753
|
+
|
|
754
|
+
## Image Input
|
|
755
|
+
|
|
756
|
+
Models with vision capabilities can process images. You can check if a model supports images via the `input` property. If you pass images to a non-vision model, they are silently ignored.
|
|
757
|
+
|
|
758
|
+
```typescript
|
|
759
|
+
import { readFileSync } from 'fs';
|
|
760
|
+
|
|
761
|
+
const model = models.getModel('openai', 'gpt-4o-mini')!;
|
|
762
|
+
|
|
763
|
+
// Check if model supports images
|
|
764
|
+
if (model.input.includes('image')) {
|
|
765
|
+
console.log('Model supports vision');
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const imageBuffer = readFileSync('image.png');
|
|
769
|
+
const base64Image = imageBuffer.toString('base64');
|
|
770
|
+
|
|
771
|
+
const response = await models.complete(model, {
|
|
772
|
+
messages: [{
|
|
773
|
+
role: 'user',
|
|
774
|
+
content: [
|
|
775
|
+
{ type: 'text', text: 'What is in this image?' },
|
|
776
|
+
{ type: 'image', data: base64Image, mimeType: 'image/png' }
|
|
777
|
+
],
|
|
778
|
+
timestamp: Date.now()
|
|
779
|
+
}]
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
// Access the response
|
|
783
|
+
for (const block of response.content) {
|
|
784
|
+
if (block.type === 'text') {
|
|
785
|
+
console.log(block.text);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
## Image Generation
|
|
791
|
+
|
|
792
|
+
Image models live in the same `Models` collection and on the same `Provider` as chat models, so one credential per provider covers both. They are typed `ImageModel` with `type: "image"` and are used through `generateImages()`, a one-shot API that waits for the provider response and returns the final `AssistantImages` result. Do not use the chat/stream APIs for them; `stream()` rejects image models.
|
|
793
|
+
|
|
794
|
+
### Basic Image Generation
|
|
795
|
+
|
|
796
|
+
```typescript
|
|
797
|
+
import { builtinModels } from '@panticonic/pi-ai/providers/all';
|
|
798
|
+
|
|
799
|
+
const models = builtinModels();
|
|
800
|
+
|
|
801
|
+
const model = models.getModelOfType('image', 'openrouter', 'google/gemini-2.5-flash-image')!;
|
|
802
|
+
|
|
803
|
+
// Auth resolves through the provider (OPENROUTER_API_KEY here); explicit apiKey wins
|
|
804
|
+
const result = await models.generateImages(model, {
|
|
805
|
+
input: [{ type: 'text', text: 'Generate a red circle on a plain white background.' }]
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
for (const block of result.output) {
|
|
809
|
+
if (block.type === 'text') {
|
|
810
|
+
console.log(block.text);
|
|
811
|
+
} else if (block.type === 'image') {
|
|
812
|
+
console.log(block.mimeType);
|
|
813
|
+
console.log(block.data.substring(0, 32));
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
`generateImages()` accepts only `ImageModel` values. If an upstream model supports both chat and image generation, the catalog contains separate entries with the same provider and ID: `getModel()` returns its chat operation and `getModelOfType('image', ...)` returns its image operation. Failures never reject; they return an `AssistantImages` with `stopReason: "error"`, including unknown providers, unconfigured auth, and providers without an image implementation.
|
|
819
|
+
|
|
820
|
+
A provider declares image support with the `images` option of [`createProvider()`](#createprovider): a map from `model.api` to an implementation with `generateImages()`. Image models go into the same `models` list as chat models. `api` becomes optional when `images` is present, so an image-only provider is just a provider without chat models:
|
|
821
|
+
|
|
822
|
+
```typescript
|
|
823
|
+
import { createProvider, envApiKeyAuth } from '@panticonic/pi-ai';
|
|
824
|
+
|
|
825
|
+
const pixels = createProvider({
|
|
826
|
+
id: 'pixels',
|
|
827
|
+
auth: { apiKey: envApiKeyAuth('Pixels API key', ['PIXELS_API_KEY']) },
|
|
828
|
+
models: [{
|
|
829
|
+
type: 'image',
|
|
830
|
+
id: 'flux-pro',
|
|
831
|
+
name: 'FLUX Pro',
|
|
832
|
+
api: 'pixels-images',
|
|
833
|
+
provider: 'pixels',
|
|
834
|
+
baseUrl: 'https://api.pixels.test/v1',
|
|
835
|
+
input: ['text'],
|
|
836
|
+
output: ['image'],
|
|
837
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
838
|
+
}],
|
|
839
|
+
images: {
|
|
840
|
+
'pixels-images': { generateImages: async (model, context, options) => { /* ... */ } },
|
|
841
|
+
},
|
|
842
|
+
});
|
|
843
|
+
models.setProvider(pixels);
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
The old global API (`getImageModel()` / `getImageModels()` / `getImageProviders()` / `generateImages()`) remains available on the [compat entrypoint](#migrating-from-the-old-global-api):
|
|
847
|
+
|
|
848
|
+
```typescript
|
|
849
|
+
import { getImageModel, generateImages } from '@panticonic/pi-ai/compat';
|
|
850
|
+
|
|
851
|
+
const model = getImageModel('openrouter', 'google/gemini-2.5-flash-image');
|
|
852
|
+
const result = await generateImages(model, {
|
|
853
|
+
input: [{ type: 'text', text: 'Generate a red circle on a plain white background.' }]
|
|
854
|
+
}, {
|
|
855
|
+
apiKey: process.env.OPENROUTER_API_KEY
|
|
856
|
+
});
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
Some models also support image input:
|
|
860
|
+
|
|
861
|
+
```typescript
|
|
862
|
+
import { readFileSync } from 'fs';
|
|
863
|
+
|
|
864
|
+
const imageBuffer = readFileSync('input.png');
|
|
865
|
+
const result = await models.generateImages(model, {
|
|
866
|
+
input: [
|
|
867
|
+
{ type: 'text', text: 'Create a variation of this image with a blue background.' },
|
|
868
|
+
{ type: 'image', data: imageBuffer.toString('base64'), mimeType: 'image/png' }
|
|
869
|
+
]
|
|
870
|
+
});
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
Check capabilities on the model metadata:
|
|
874
|
+
|
|
875
|
+
```typescript
|
|
876
|
+
console.log(model.input); // ['text'] or ['text', 'image']
|
|
877
|
+
console.log(model.output); // ['image'] or ['image', 'text']
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
### Notes and Limitations
|
|
881
|
+
|
|
882
|
+
- Image models and chat models share `Models` and `Provider`; list them with `getModelsOfType('image')` and run them with `generateImages()`, never the chat/stream APIs.
|
|
883
|
+
- Image-generation models do not participate in tool calling.
|
|
884
|
+
- Outputs are returned in `AssistantImages.output` and can include both base64-encoded `ImageContent` blocks and `TextContent` blocks.
|
|
885
|
+
- Some models return only images, others return images plus text. Check `model.output`.
|
|
886
|
+
- Some models accept image input, others are text-to-image only. Check `model.input`.
|
|
887
|
+
- Like the streaming APIs, image generation supports options such as `apiKey`, `signal`, `headers`, `onPayload`, and `onResponse`, and results may include `stopReason`, `responseId`, and `usage`.
|
|
888
|
+
- If you want a model to analyze images in a conversation or call tools, use the regular chat APIs with a model that supports image input.
|
|
889
|
+
- At the moment, image generation is available through only one provider, OpenRouter.
|
|
890
|
+
|
|
891
|
+
## Classification
|
|
892
|
+
|
|
893
|
+
Classifier models consume structured JSON state and answer one or more typed questions. They do not use chat or image-generation APIs. TypeSafe's Jev model is available from these built-in providers:
|
|
894
|
+
|
|
895
|
+
| Provider | Model IDs | Auth |
|
|
896
|
+
| --- | --- | --- |
|
|
897
|
+
| `typesafe` | `jev-latest` | `TYPESAFE_API_KEY` |
|
|
898
|
+
| `openrouter` | `typesafe/jev-1.13`, `~typesafe/jev-latest` | `OPENROUTER_API_KEY` or OpenRouter OAuth |
|
|
899
|
+
| `cloudflare-workers-ai` | `typesafe/jev` | `CLOUDFLARE_API_KEY` and `CLOUDFLARE_ACCOUNT_ID` |
|
|
900
|
+
| `vercel-ai-gateway` | `typesafe-ai/jev` | `AI_GATEWAY_API_KEY` |
|
|
901
|
+
| `opencode` | `jev-1.13`, `jev-1.13-free` | `OPENCODE_API_KEY` |
|
|
902
|
+
|
|
903
|
+
```typescript
|
|
904
|
+
import { builtinModels } from '@panticonic/pi-ai/providers/all';
|
|
905
|
+
|
|
906
|
+
const models = builtinModels();
|
|
907
|
+
const model = models.getModelOfType('classifier', 'typesafe', 'jev-latest')!;
|
|
908
|
+
const result = await models.classify(model, {
|
|
909
|
+
state: { message: 'The change works perfectly, thanks.' },
|
|
910
|
+
questions: {
|
|
911
|
+
category: {
|
|
912
|
+
type: 'choice',
|
|
913
|
+
instructions: 'Classify the message.',
|
|
914
|
+
criteria: {
|
|
915
|
+
approval: 'The user approves of the result',
|
|
916
|
+
correction: 'The user requests a correction'
|
|
917
|
+
}
|
|
918
|
+
},
|
|
919
|
+
satisfaction: {
|
|
920
|
+
type: 'score',
|
|
921
|
+
instructions: 'Score user satisfaction.',
|
|
922
|
+
criteria: ['dissatisfied', 'neutral', 'satisfied']
|
|
923
|
+
},
|
|
924
|
+
approved: {
|
|
925
|
+
type: 'bool',
|
|
926
|
+
instructions: 'Does the user approve?',
|
|
927
|
+
criteria: { true: 'Approval', false: 'No approval' }
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
console.log(result.answers);
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
The public contract uses `bool` questions and `{ type: "bool", probability }` answers. The TypeSafe adapter translates those to and from its `noul` wire representation. Like image generation, `classify()` resolves to a result with `stopReason: "error"` instead of rejecting for provider, authentication, or response errors.
|
|
936
|
+
|
|
937
|
+
When the service reports token counts, `result.usage` carries them with their cost at the model's catalog price, the same `Usage` shape as chat messages. All System One services report token counts; a request that was answered with malformed answers keeps its usage. Local classifiers such as `llama-cpp-classify` report no usage.
|
|
938
|
+
|
|
939
|
+
`ClassifierOptions.temperature` divides the answer logits by the given value before they are normalized; values above 1 soften the distribution. APIs that cannot apply it, such as System One, ignore it.
|
|
940
|
+
|
|
941
|
+
### Chat models on llama.cpp
|
|
942
|
+
|
|
943
|
+
The `llama-cpp-classify` API turns a chat model served by llama.cpp's `llama-server` into a classifier. Each question becomes one chat prompt: the state, every question of the request, the state again, and the question with its answers under single-token labels (letters for a choice, `Yes`/`No` for a bool, digits for a score). The prompt up to the final question is shared by all questions of a request, so the server's prompt cache evaluates the state once per request. The server returns the log-probabilities of the next token, and the answer is the softmax over the label tokens. Choices support up to 62 options and scores up to 10 levels. The model's `baseUrl` is the server URL; a trailing `/v1` is ignored. In router mode, the model ID selects the model.
|
|
944
|
+
|
|
945
|
+
```typescript
|
|
946
|
+
import { createProvider } from '@panticonic/pi-ai';
|
|
947
|
+
import { llamaCppClassifyApi } from '@panticonic/pi-ai/api/llama-cpp-classify.lazy';
|
|
948
|
+
|
|
949
|
+
const provider = createProvider({
|
|
950
|
+
id: 'local-llama',
|
|
951
|
+
auth: { apiKey: { name: 'llama.cpp', resolve: async () => ({ auth: {} }) } },
|
|
952
|
+
models: [{
|
|
953
|
+
type: 'classifier',
|
|
954
|
+
id: 'qwen3-4b',
|
|
955
|
+
name: 'Qwen3 4B',
|
|
956
|
+
api: 'llama-cpp-classify',
|
|
957
|
+
provider: 'local-llama',
|
|
958
|
+
baseUrl: 'http://127.0.0.1:8080',
|
|
959
|
+
input: ['text'],
|
|
960
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
961
|
+
contextWindow: 32768
|
|
962
|
+
}],
|
|
963
|
+
classifiers: { 'llama-cpp-classify': llamaCppClassifyApi() }
|
|
964
|
+
});
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
Raw label probabilities are usually overconfident; pass `temperature` above 1 to soften them.
|
|
968
|
+
|
|
969
|
+
Custom providers register classifier models and implementations by API ID:
|
|
970
|
+
|
|
971
|
+
```typescript
|
|
972
|
+
createProvider({
|
|
973
|
+
id: 'classifier-service',
|
|
974
|
+
auth,
|
|
975
|
+
models: [model],
|
|
976
|
+
classifiers: {
|
|
977
|
+
'classifier-api': { classify: async (model, context, options) => result }
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
## Thinking/Reasoning
|
|
983
|
+
|
|
984
|
+
Many models support thinking/reasoning capabilities where they can show their internal thought process. You can check if a model supports reasoning via the `reasoning` property. If you pass reasoning options to a non-reasoning model, they are silently ignored.
|
|
985
|
+
|
|
986
|
+
### Unified Interface (streamSimple/completeSimple)
|
|
987
|
+
|
|
988
|
+
```typescript
|
|
989
|
+
// Many models across providers support thinking/reasoning
|
|
990
|
+
const model = models.getModel('anthropic', 'claude-sonnet-4-5')!;
|
|
991
|
+
// or models.getModel('openai', 'gpt-5-mini');
|
|
992
|
+
// or models.getModel('google', 'gemini-2.5-flash');
|
|
993
|
+
// or models.getModel('xai', 'grok-4.7');
|
|
994
|
+
|
|
995
|
+
// Check if model supports reasoning
|
|
996
|
+
if (model.reasoning) {
|
|
997
|
+
console.log('Model supports reasoning/thinking');
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// Use the simplified reasoning option
|
|
1001
|
+
const response = await models.completeSimple(model, {
|
|
1002
|
+
messages: [{ role: 'user', content: 'Solve: 2x + 5 = 13', timestamp: Date.now() }]
|
|
1003
|
+
}, {
|
|
1004
|
+
reasoning: 'medium' // 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
// Access thinking and text blocks
|
|
1008
|
+
for (const block of response.content) {
|
|
1009
|
+
if (block.type === 'thinking') {
|
|
1010
|
+
console.log('Thinking:', block.thinking);
|
|
1011
|
+
} else if (block.type === 'text') {
|
|
1012
|
+
console.log('Response:', block.text);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
```
|
|
1016
|
+
|
|
1017
|
+
`xhigh` and `max` are model-specific, opt-in levels. Use `getSupportedThinkingLevels(model)` to determine whether a concrete model exposes either level; models such as GPT-5.6 can expose both.
|
|
1018
|
+
|
|
1019
|
+
### Provider-Specific Options (stream/complete)
|
|
1020
|
+
|
|
1021
|
+
`models.stream()`/`complete()` accept the owning API's full option set. Use `hasApi()` to narrow a dynamically looked-up model to its API for full option typing:
|
|
1022
|
+
|
|
1023
|
+
```typescript
|
|
1024
|
+
import { hasApi } from '@panticonic/pi-ai';
|
|
1025
|
+
|
|
1026
|
+
// OpenAI Reasoning (o1, o3, gpt-5)
|
|
1027
|
+
const openaiModel = models.getModel('openai', 'gpt-5-mini')!;
|
|
1028
|
+
if (hasApi(openaiModel, 'openai-responses')) {
|
|
1029
|
+
await models.complete(openaiModel, context, {
|
|
1030
|
+
reasoningEffort: 'medium',
|
|
1031
|
+
reasoningSummary: 'detailed' // OpenAI Responses API only
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// Anthropic Thinking
|
|
1036
|
+
const anthropicModel = models.getModel('anthropic', 'claude-sonnet-4-5')!;
|
|
1037
|
+
if (hasApi(anthropicModel, 'anthropic-messages')) {
|
|
1038
|
+
await models.complete(anthropicModel, context, {
|
|
1039
|
+
thinkingEnabled: true,
|
|
1040
|
+
thinkingBudgetTokens: 8192 // Optional token limit
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// Google Gemini Thinking
|
|
1045
|
+
const googleModel = models.getModel('google', 'gemini-2.5-flash')!;
|
|
1046
|
+
if (hasApi(googleModel, 'google-generative-ai')) {
|
|
1047
|
+
await models.complete(googleModel, context, {
|
|
1048
|
+
thinking: {
|
|
1049
|
+
enabled: true,
|
|
1050
|
+
budgetTokens: 8192 // -1 for dynamic, 0 to disable
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
### Streaming Thinking Content
|
|
1057
|
+
|
|
1058
|
+
When streaming, thinking content is delivered through specific events:
|
|
1059
|
+
|
|
1060
|
+
```typescript
|
|
1061
|
+
const s = models.streamSimple(model, context, { reasoning: 'high' });
|
|
1062
|
+
|
|
1063
|
+
for await (const event of s) {
|
|
1064
|
+
switch (event.type) {
|
|
1065
|
+
case 'thinking_start':
|
|
1066
|
+
console.log('[Model started thinking]');
|
|
1067
|
+
break;
|
|
1068
|
+
case 'thinking_delta':
|
|
1069
|
+
process.stdout.write(event.delta); // Stream thinking content
|
|
1070
|
+
break;
|
|
1071
|
+
case 'thinking_end':
|
|
1072
|
+
console.log('\n[Thinking complete]');
|
|
1073
|
+
break;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
## Stop Reasons
|
|
1079
|
+
|
|
1080
|
+
Every `AssistantMessage` includes a `stopReason` field that indicates how the generation ended:
|
|
1081
|
+
|
|
1082
|
+
- `"pending"` - Only present in partial messages when we do not know what the stop reason will be
|
|
1083
|
+
- `"stop"` - This is the final message the model will produce this turn
|
|
1084
|
+
- `"length"` - Output hit the maximum token limit
|
|
1085
|
+
- `"toolUse"` - Model is calling tools and expects tool results
|
|
1086
|
+
- `"error"` - An error occurred during generation
|
|
1087
|
+
- `"aborted"` - Request was cancelled via abort signal
|
|
1088
|
+
|
|
1089
|
+
`AssistantMessage` may also include `responseId`, a provider-specific upstream response or message identifier when the underlying API exposes one. Do not assume it is always present across providers.
|
|
1090
|
+
|
|
1091
|
+
## Error Handling
|
|
1092
|
+
|
|
1093
|
+
Request failures after a stream is returned never throw: when a request ends with an error (including aborts and tool call validation errors), the streaming API emits an error event and the final message carries the details. Setup failures may emit `error` without `start`; failures after generation begins emit `start`, any observed updates, then `error`. Direct API `streamSimple()` calls throw synchronously when request auth is missing:
|
|
1094
|
+
|
|
1095
|
+
```typescript
|
|
1096
|
+
// In streaming
|
|
1097
|
+
for await (const event of s) {
|
|
1098
|
+
if (event.type === 'error') {
|
|
1099
|
+
// event.reason is either "error" or "aborted"
|
|
1100
|
+
// event.error is the AssistantMessage with partial content
|
|
1101
|
+
console.error(`Error (${event.reason}):`, event.error.errorMessage);
|
|
1102
|
+
console.log('Partial content:', event.error.content);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// The final message will have the error details
|
|
1107
|
+
const message = await s.result();
|
|
1108
|
+
if (message.stopReason === 'error' || message.stopReason === 'aborted') {
|
|
1109
|
+
console.error('Request failed:', message.errorMessage);
|
|
1110
|
+
// message.content contains any partial content received before the error
|
|
1111
|
+
// message.usage contains partial token counts and costs
|
|
1112
|
+
}
|
|
1113
|
+
```
|
|
1114
|
+
|
|
1115
|
+
When using a provider collection, auth failures (OAuth refresh failed, unknown provider) surface as a stream error with `stopReason: "error"`. Direct API `streamSimple()` calls instead throw synchronously when their required auth is absent.
|
|
1116
|
+
|
|
1117
|
+
### Aborting Requests
|
|
1118
|
+
|
|
1119
|
+
The abort signal allows you to cancel in-progress requests. Aborted requests have `stopReason === 'aborted'`:
|
|
1120
|
+
|
|
1121
|
+
```typescript
|
|
1122
|
+
const controller = new AbortController();
|
|
1123
|
+
|
|
1124
|
+
// Abort after 2 seconds
|
|
1125
|
+
setTimeout(() => controller.abort(), 2000);
|
|
1126
|
+
|
|
1127
|
+
const s = models.stream(model, {
|
|
1128
|
+
messages: [{ role: 'user', content: 'Write a long story', timestamp: Date.now() }]
|
|
1129
|
+
}, {
|
|
1130
|
+
signal: controller.signal
|
|
1131
|
+
});
|
|
1132
|
+
|
|
1133
|
+
for await (const event of s) {
|
|
1134
|
+
if (event.type === 'text_delta') {
|
|
1135
|
+
process.stdout.write(event.delta);
|
|
1136
|
+
} else if (event.type === 'error') {
|
|
1137
|
+
// event.reason tells you if it was "error" or "aborted"
|
|
1138
|
+
console.log(`${event.reason === 'aborted' ? 'Aborted' : 'Error'}:`, event.error.errorMessage);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// Get results (may be partial if aborted)
|
|
1143
|
+
const response = await s.result();
|
|
1144
|
+
if (response.stopReason === 'aborted') {
|
|
1145
|
+
console.log('Request was aborted:', response.errorMessage);
|
|
1146
|
+
console.log('Partial content received:', response.content);
|
|
1147
|
+
console.log('Tokens used:', response.usage);
|
|
1148
|
+
}
|
|
1149
|
+
```
|
|
1150
|
+
|
|
1151
|
+
### Continuing After Abort
|
|
1152
|
+
|
|
1153
|
+
Aborted messages can be added to the conversation context and continued in subsequent requests:
|
|
1154
|
+
|
|
1155
|
+
```typescript
|
|
1156
|
+
const context = {
|
|
1157
|
+
messages: [
|
|
1158
|
+
{ role: 'user', content: 'Explain quantum computing in detail', timestamp: Date.now() }
|
|
1159
|
+
]
|
|
1160
|
+
};
|
|
1161
|
+
|
|
1162
|
+
// First request gets aborted after 2 seconds
|
|
1163
|
+
const controller1 = new AbortController();
|
|
1164
|
+
setTimeout(() => controller1.abort(), 2000);
|
|
1165
|
+
|
|
1166
|
+
const partial = await models.complete(model, context, { signal: controller1.signal });
|
|
1167
|
+
|
|
1168
|
+
// Add the partial response to context
|
|
1169
|
+
context.messages.push(partial);
|
|
1170
|
+
context.messages.push({ role: 'user', content: 'Please continue', timestamp: Date.now() });
|
|
1171
|
+
|
|
1172
|
+
// Continue the conversation
|
|
1173
|
+
const continuation = await models.complete(model, context);
|
|
1174
|
+
```
|
|
1175
|
+
|
|
1176
|
+
### Debugging Provider Payloads
|
|
1177
|
+
|
|
1178
|
+
Use the `onPayload` callback to inspect the request payload sent to the provider. This is useful for debugging request formatting issues or provider validation errors.
|
|
1179
|
+
|
|
1180
|
+
```typescript
|
|
1181
|
+
const response = await models.complete(model, context, {
|
|
1182
|
+
onPayload: (payload) => {
|
|
1183
|
+
console.log('Provider payload:', JSON.stringify(payload, null, 2));
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1186
|
+
```
|
|
1187
|
+
|
|
1188
|
+
The callback is supported by `stream`, `complete`, `streamSimple`, and `completeSimple`.
|
|
1189
|
+
|
|
1190
|
+
### Observing Provider Stream Events
|
|
1191
|
+
|
|
1192
|
+
Use `onProviderStreamEvent` to inspect provider-specific fields that Pi does not include in `AssistantMessage`. The callback receives the parsed event available to the adapter before Pi normalizes it. Treat the event as read-only because mutations can affect normalization. This is not guaranteed to be the original HTTP bytes or SSE frame.
|
|
1193
|
+
|
|
1194
|
+
```typescript
|
|
1195
|
+
const openRouterModel = models.getModel('openrouter', 'openrouter/auto')!;
|
|
1196
|
+
const response = await models.complete(openRouterModel, context, {
|
|
1197
|
+
headers: { "X-OpenRouter-Metadata": "enabled" },
|
|
1198
|
+
onProviderStreamEvent: (data) => {
|
|
1199
|
+
const chunk = data as Record<string, unknown>;
|
|
1200
|
+
if (chunk.openrouter_metadata) {
|
|
1201
|
+
console.log(chunk.openrouter_metadata);
|
|
1202
|
+
}
|
|
1203
|
+
},
|
|
1204
|
+
});
|
|
1205
|
+
```
|
|
1206
|
+
|
|
1207
|
+
Callbacks are awaited in stream order, so slow callbacks delay stream consumption and thrown errors fail the request. SDK-backed adapters can expose only fields retained by their SDK.
|
|
1208
|
+
|
|
1209
|
+
## Custom Providers
|
|
1210
|
+
|
|
1211
|
+
### createProvider()
|
|
1212
|
+
|
|
1213
|
+
`createProvider()` builds a provider from parts: identity, auth, a model list, and an API implementation (`api` for chat models, `images` for image generation, `classifiers` for classification; at least one is required, see [Image Generation](#image-generation)). Use it for local inference servers, proxies, or any OpenAI/Anthropic-compatible endpoint:
|
|
1214
|
+
|
|
1215
|
+
```typescript
|
|
1216
|
+
import { createModels, createProvider, envApiKeyAuth, type Model } from '@panticonic/pi-ai';
|
|
1217
|
+
import { openAICompletionsApi } from '@panticonic/pi-ai/api/openai-completions.lazy';
|
|
1218
|
+
|
|
1219
|
+
const ollamaModel: Model<'openai-completions'> = {
|
|
1220
|
+
id: 'llama-3.1-8b',
|
|
1221
|
+
name: 'Llama 3.1 8B (Ollama)',
|
|
1222
|
+
api: 'openai-completions',
|
|
1223
|
+
provider: 'ollama',
|
|
1224
|
+
baseUrl: 'http://localhost:11434/v1',
|
|
1225
|
+
reasoning: false,
|
|
1226
|
+
input: ['text'],
|
|
1227
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
1228
|
+
contextWindow: 128000,
|
|
1229
|
+
maxTokens: 32000
|
|
1230
|
+
};
|
|
1231
|
+
|
|
1232
|
+
const ollama = createProvider({
|
|
1233
|
+
id: 'ollama',
|
|
1234
|
+
name: 'Ollama',
|
|
1235
|
+
baseUrl: 'http://localhost:11434/v1',
|
|
1236
|
+
// Every provider declares auth; keyless local servers resolve as configured with no key.
|
|
1237
|
+
auth: { apiKey: { name: 'Ollama', resolve: async () => ({ auth: {} }) } },
|
|
1238
|
+
models: [ollamaModel],
|
|
1239
|
+
api: openAICompletionsApi(),
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1242
|
+
const models = createModels();
|
|
1243
|
+
models.setProvider(ollama);
|
|
1244
|
+
|
|
1245
|
+
await models.complete(models.getModel('ollama', 'llama-3.1-8b')!, context);
|
|
1246
|
+
```
|
|
1247
|
+
|
|
1248
|
+
For providers with real keys, `envApiKeyAuth(displayName, envVars)` gives the standard behavior (stored credential wins, then the first set env var):
|
|
1249
|
+
|
|
1250
|
+
```typescript
|
|
1251
|
+
const proxy = createProvider({
|
|
1252
|
+
id: 'my-proxy',
|
|
1253
|
+
auth: { apiKey: envApiKeyAuth('My proxy API key', ['MY_PROXY_API_KEY']) },
|
|
1254
|
+
models: [/* ... */],
|
|
1255
|
+
api: openAICompletionsApi(),
|
|
1256
|
+
});
|
|
1257
|
+
```
|
|
1258
|
+
|
|
1259
|
+
Mixed-API providers pass a map keyed by `model.api`; each model dispatches to its API's implementation:
|
|
1260
|
+
|
|
1261
|
+
```typescript
|
|
1262
|
+
import { anthropicMessagesApi } from '@panticonic/pi-ai/api/anthropic-messages.lazy';
|
|
1263
|
+
import { openAIResponsesApi } from '@panticonic/pi-ai/api/openai-responses.lazy';
|
|
1264
|
+
|
|
1265
|
+
const gateway = createProvider({
|
|
1266
|
+
id: 'my-gateway',
|
|
1267
|
+
auth: { apiKey: envApiKeyAuth('Gateway key', ['GATEWAY_API_KEY']) },
|
|
1268
|
+
models: [/* models with api: 'anthropic-messages' or 'openai-responses' */],
|
|
1269
|
+
api: {
|
|
1270
|
+
'anthropic-messages': anthropicMessagesApi(),
|
|
1271
|
+
'openai-responses': openAIResponsesApi(),
|
|
1272
|
+
},
|
|
1273
|
+
});
|
|
1274
|
+
```
|
|
1275
|
+
|
|
1276
|
+
Provider-wide endpoint or request transformations belong in the provider's API implementation: wrap the `ProviderStreams` you pass as `api` so every request goes through the transformation before dispatch. The Cloudflare providers do this to materialize account/gateway endpoint placeholders from the resolved provider env:
|
|
1277
|
+
|
|
1278
|
+
```typescript
|
|
1279
|
+
function tenantStreams(streams: ProviderStreams): ProviderStreams {
|
|
1280
|
+
const withTenant = (model: Model<Api>) => ({ ...model, baseUrl: model.baseUrl.replace('{tenant}', tenantId) });
|
|
1281
|
+
return {
|
|
1282
|
+
stream: (model, context, options) => streams.stream(withTenant(model), context, options),
|
|
1283
|
+
streamSimple: (model, context, options) => streams.streamSimple(withTenant(model), context, options),
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
const tenantGateway = createProvider({
|
|
1288
|
+
id: 'tenant-gateway',
|
|
1289
|
+
auth: { apiKey: envApiKeyAuth('Gateway key', ['GATEWAY_API_KEY']) },
|
|
1290
|
+
models: [/* ... */],
|
|
1291
|
+
api: tenantStreams(openAICompletionsApi()),
|
|
1292
|
+
});
|
|
1293
|
+
```
|
|
1294
|
+
|
|
1295
|
+
Dynamic model lists use `fetchModels`, which can return models of every type. `Models.refresh()` refreshes every configured dynamic provider, passing its effective API-key or refreshed OAuth credential. A `ModelsStore` persists dynamic catalogs; both stores default to in-memory implementations. Its `read`, `write`, and `delete` operations accept optional cancellation, and `Models` binds those waits to the provider refresh signal.
|
|
1296
|
+
|
|
1297
|
+
```typescript
|
|
1298
|
+
const models = createModels({ credentials, modelsStore });
|
|
1299
|
+
const llamacpp = createProvider({
|
|
1300
|
+
id: 'llamacpp',
|
|
1301
|
+
auth: { apiKey: { name: 'llama.cpp', resolve: async () => ({ auth: {} }) } },
|
|
1302
|
+
models: [],
|
|
1303
|
+
fetchModels: async ({ signal }) => fetchModelsFromServer('http://localhost:8080', signal),
|
|
1304
|
+
api: openAICompletionsApi(),
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
models.setProvider(llamacpp);
|
|
1308
|
+
const result = await models.refresh({ signal });
|
|
1309
|
+
if (result.aborted) console.log('refresh cancelled');
|
|
1310
|
+
for (const [provider, error] of result.errors) console.error(provider, error);
|
|
1311
|
+
```
|
|
1312
|
+
|
|
1313
|
+
`Models.refresh()` is unbounded when its optional signal is omitted. Providers always receive a concrete `RefreshModelsContext.signal` and must honor it for network requests and other blocking work. When a caller supplies a signal, `Models.refresh()` returns promptly with `aborted: true` after cancellation even if a custom provider fails to cooperate; the provider must still honor the signal to stop its underlying work.
|
|
1314
|
+
|
|
1315
|
+
Use `models.refresh({ providers: ['openrouter'] })` to restrict work to selected providers, `models.refresh({ allowNetwork: false })` to restore persisted catalogs without network access, or `models.refresh({ force: true })` to bypass provider freshness checks. Model reads stay synchronous and return the last restored or refreshed list.
|
|
1316
|
+
|
|
1317
|
+
`createProvider()` handles dynamic publication and persistence automatically. Handwritten `Provider.refreshModels()` implementations receive the read-only `context.stored` snapshot and publish through `context.publish({ persist?, update? })`. Omit `persist` to leave storage unchanged, pass a `ModelsStoreEntry` to write it, or pass `persist: null` to delete it. `ModelsStoreEntry.models` contains models of every type. Publication is generation-checked; put synchronous in-memory catalog changes in `update` rather than mutating state before publication.
|
|
1318
|
+
|
|
1319
|
+
Custom models can carry `headers` (e.g. proxies behind bot detection) and `compat` flags. `Models.getAuth(model)` includes those model headers, and stream methods merge them before explicit request headers and `transformHeaders`. See [OpenAI Compatibility Settings](#openai-compatibility-settings).
|
|
1320
|
+
|
|
1321
|
+
Some OpenAI-compatible servers do not understand the `developer` role used for reasoning-capable models. For those providers, set `compat.supportsDeveloperRole` to `false` so the system prompt is sent as a `system` message instead. If the server also does not support `reasoning_effort`, set `compat.supportsReasoningEffort` to `false` too. This commonly applies to Ollama, vLLM, SGLang, and similar OpenAI-compatible servers.
|
|
1322
|
+
|
|
1323
|
+
Use model-level `thinkingLevelMap` to describe model-specific thinking controls. Keys are pi thinking levels (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`). Missing standard levels through `high` use provider defaults; `xhigh` and `max` are opt-in and require a non-null map entry. String values are sent to the provider, `null` marks a level unsupported, and maps may skip levels.
|
|
1324
|
+
|
|
1325
|
+
```typescript
|
|
1326
|
+
const ollamaReasoningModel: Model<'openai-completions'> = {
|
|
1327
|
+
id: 'gpt-oss:20b',
|
|
1328
|
+
name: 'GPT-OSS 20B (Ollama)',
|
|
1329
|
+
api: 'openai-completions',
|
|
1330
|
+
provider: 'ollama',
|
|
1331
|
+
baseUrl: 'http://localhost:11434/v1',
|
|
1332
|
+
reasoning: true,
|
|
1333
|
+
input: ['text'],
|
|
1334
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
1335
|
+
contextWindow: 131072,
|
|
1336
|
+
maxTokens: 32000,
|
|
1337
|
+
thinkingLevelMap: {
|
|
1338
|
+
minimal: null,
|
|
1339
|
+
low: null,
|
|
1340
|
+
medium: null,
|
|
1341
|
+
high: 'high',
|
|
1342
|
+
xhigh: null,
|
|
1343
|
+
},
|
|
1344
|
+
compat: {
|
|
1345
|
+
supportsDeveloperRole: false,
|
|
1346
|
+
supportsReasoningEffort: false,
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
1349
|
+
```
|
|
1350
|
+
|
|
1351
|
+
### Calling API Implementations Directly
|
|
1352
|
+
|
|
1353
|
+
The API implementations are importable on their own. Each module exports exactly `stream` and `streamSimple` with that API's full option typing. Direct calls bypass provider auth and context normalization — pass `apiKey` explicitly and wrap the context in `normalizeContext()`:
|
|
1354
|
+
|
|
1355
|
+
```typescript
|
|
1356
|
+
import { normalizeContext } from '@panticonic/pi-ai';
|
|
1357
|
+
import { stream } from '@panticonic/pi-ai/api/anthropic-messages';
|
|
1358
|
+
|
|
1359
|
+
const s = stream(claudeModel, normalizeContext(context), {
|
|
1360
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
1361
|
+
thinkingEnabled: true,
|
|
1362
|
+
thinkingBudgetTokens: 2048,
|
|
1363
|
+
});
|
|
1364
|
+
```
|
|
1365
|
+
|
|
1366
|
+
Built-in API implementations live under `./api/<api-id>`:
|
|
1367
|
+
|
|
1368
|
+
| API id | Options type |
|
|
1369
|
+
|--------|--------------|
|
|
1370
|
+
| `anthropic-messages` | `AnthropicOptions` |
|
|
1371
|
+
| `openai-completions` | `OpenAICompletionsOptions` |
|
|
1372
|
+
| `openai-responses` | `OpenAIResponsesOptions` |
|
|
1373
|
+
| `openai-codex-responses` | `OpenAICodexResponsesOptions` |
|
|
1374
|
+
| `azure-openai-responses` | `AzureOpenAIResponsesOptions` |
|
|
1375
|
+
| `google-generative-ai` | `GoogleOptions` |
|
|
1376
|
+
| `google-vertex` | `GoogleVertexOptions` |
|
|
1377
|
+
| `mistral-conversations` | `MistralOptions` |
|
|
1378
|
+
| `bedrock-converse-stream` | `BedrockOptions` |
|
|
1379
|
+
|
|
1380
|
+
Importing an implementation module loads its SDK. The `./api/<id>.lazy` wrappers (used by the provider factories) defer that load to the first request when the runtime or bundler supports dynamic import chunking. Legacy raw API subpaths from older releases (`./anthropic`, `./google`, `./mistral`, `./openai-completions`, ...) were removed; use `@panticonic/pi-ai/api/<api-id>`.
|
|
1381
|
+
|
|
1382
|
+
### OpenAI Compatibility Settings
|
|
1383
|
+
|
|
1384
|
+
The `openai-completions` API is implemented by many providers with minor differences. By default, the library auto-detects compatibility settings based on `baseUrl` for a small set of known OpenAI-compatible providers (Cerebras, xAI, Chutes, DeepSeek, NVIDIA NIM, Together AI, zAi, OpenCode, Cloudflare Workers AI, etc.). For custom proxies or unknown endpoints, you can override these settings via the `compat` field. For `openai-responses` models, the compat field supports Responses-specific flags.
|
|
1385
|
+
|
|
1386
|
+
```typescript
|
|
1387
|
+
interface OpenAICompletionsCompat {
|
|
1388
|
+
supportsStore?: boolean; // Whether provider supports the `store` field (default: true)
|
|
1389
|
+
supportsDeveloperRole?: boolean; // Whether provider supports `developer` role vs `system` (default: true)
|
|
1390
|
+
supportsReasoningEffort?: boolean; // Whether provider supports `reasoning_effort` (default: true)
|
|
1391
|
+
supportsUsageInStreaming?: boolean; // Whether provider supports `stream_options: { include_usage: true }` (default: true)
|
|
1392
|
+
supportsStrictMode?: boolean; // Whether provider supports `strict` in tool definitions (default: false; enabled in metadata for capable built-in models)
|
|
1393
|
+
supportsOpenAIGrammarTools?: boolean; // Whether to emit OpenAI custom Lark/regex grammar tools; false falls back to normal function tools (default: false; the generated catalog enables it for capable models)
|
|
1394
|
+
supportsMidConvoSystemMessages?: boolean; // Whether the model accepts system messages after the conversation started; false folds them into the leading prompt (default: false; the generated catalog enables it for verified models)
|
|
1395
|
+
supportsMidConvoToolAdditions?: boolean; // Whether system messages can add tools mid-conversation via Kimi-style `tools` system messages; requires supportsMidConvoSystemMessages (default: false)
|
|
1396
|
+
sendSessionAffinityHeaders?: boolean; // Send session-affinity data from `sessionId` (default: true for OpenRouter, false otherwise)
|
|
1397
|
+
sessionAffinityFormat?: 'openai' | 'openai-nosession' | 'openrouter'; // Format for session affinity: 'openai' uses `prompt_cache_key`, `session_id`, `x-client-request-id`, and `x-session-affinity`; 'openai-nosession' uses `prompt_cache_key`, `x-client-request-id`, and `x-session-affinity`; 'openrouter' uses `x-session-id` (default: auto-detected)
|
|
1398
|
+
maxTokensField?: 'max_completion_tokens' | 'max_tokens'; // Which field name to use (default: max_completion_tokens)
|
|
1399
|
+
requiresToolResultName?: boolean; // Whether tool results require the `name` field (default: false)
|
|
1400
|
+
requiresAssistantAfterToolResult?: boolean; // Whether tool results must be followed by an assistant message (default: false)
|
|
1401
|
+
requiresThinkingAsText?: boolean; // Whether thinking blocks must be converted to text (default: false)
|
|
1402
|
+
requiresReasoningContentOnAssistantMessages?: boolean; // Whether all replayed assistant messages must include empty reasoning_content when reasoning is enabled (default: auto-detected for DeepSeek)
|
|
1403
|
+
thinkingFormat?: 'openai' | 'openrouter' | 'deepseek' | 'together' | 'baseten' | 'zai' | 'qwen' | 'chat-template' | 'qwen-chat-template' | 'string-thinking' | 'ant-ling'; // Format for reasoning param: 'openai' uses reasoning_effort, 'openrouter' uses reasoning: { effort }, 'deepseek' uses thinking: { type } plus reasoning_effort when supported, 'together' uses reasoning: { enabled } plus reasoning_effort when supported, 'baseten' uses configurable chat_template_args plus reasoning_effort when supported, 'zai' uses thinking: { type }, 'qwen' uses enable_thinking, 'chat-template' uses configurable chat_template_kwargs, 'qwen-chat-template' uses chat_template_kwargs.enable_thinking and preserve_thinking, 'string-thinking' uses top-level thinking, 'ant-ling' uses reasoning: { effort } only for mapped efforts (default: openai)
|
|
1404
|
+
chatTemplateKwargs?: Record<string, string | number | boolean | null | { '$var': 'thinking.enabled' | 'thinking.effort' | 'thinking.budget'; omitWhenOff?: boolean }>; // chat_template_kwargs values; use $var for pi-controlled thinking values
|
|
1405
|
+
chatTemplateArgs?: Record<string, string | number | boolean | null | { '$var': 'thinking.enabled' | 'thinking.effort' | 'thinking.budget'; omitWhenOff?: boolean }>; // chat_template_args values for thinkingFormat: 'baseten'; use $var for pi-controlled thinking values
|
|
1406
|
+
thinkingTokenBudgetField?: 'thinking_token_budget' | 'thinking_budget' | 'thinking_budget_tokens'; // Top-level field that caps reasoning tokens from thinkingBudgets (vLLM / Qwen / llama.cpp). Off by default.
|
|
1407
|
+
supportsThinkingTokenBudget?: boolean; // Alias for thinkingTokenBudgetField: 'thinking_token_budget' (vLLM). Prefer thinkingTokenBudgetField. Default: false.
|
|
1408
|
+
cacheControlFormat?: 'anthropic'; // Anthropic-style cache_control on system prompt, last tool, and last user/assistant text content
|
|
1409
|
+
openRouterRouting?: OpenRouterRouting; // OpenRouter routing preferences (default: {})
|
|
1410
|
+
vercelGatewayRouting?: VercelGatewayRouting; // Vercel AI Gateway routing preferences (default: {})
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
interface OpenAIResponsesCompat {
|
|
1414
|
+
supportsDeveloperRole?: boolean; // Whether provider supports `developer` role vs `system` (default: true)
|
|
1415
|
+
sessionAffinityFormat?: 'openai' | 'openai-nosession' | 'openrouter'; // Session-affinity header format: 'openai' sends `session_id` and `x-client-request-id`; 'openai-nosession' sends `x-client-request-id`; 'openrouter' sends `x-session-id`. Does not affect the `prompt_cache_key` body param (default: auto-detected)
|
|
1416
|
+
supportsLongCacheRetention?: boolean; // Whether provider supports `prompt_cache_retention: "24h"` (default: true)
|
|
1417
|
+
supportsStrictMode?: boolean; // Whether provider supports strict JSON-schema function tools (default: false; enabled in metadata for built-in OpenAI models)
|
|
1418
|
+
supportsOpenAIGrammarTools?: boolean; // Whether to emit OpenAI custom Lark/regex grammar tools; false falls back to normal function tools (default: false; the generated catalog enables it for capable models)
|
|
1419
|
+
}
|
|
1420
|
+
```
|
|
1421
|
+
|
|
1422
|
+
OpenRouter requests send `x-session-id` from `sessionId` when prompt caching is enabled. Chat Completions and Anthropic Messages both auto-detect OpenRouter endpoints unless `sendSessionAffinityHeaders` is explicitly false. On Anthropic-compatible models, `sessionAffinityFormat: "openrouter"` selects `x-session-id`; when unset, the existing `x-session-affinity` format is used. Explicit request headers take precedence over generated headers.
|
|
1423
|
+
|
|
1424
|
+
If `compat` is not set, the library falls back to URL-based detection. If `compat` is partially set, unspecified fields use the detected defaults. This is useful for:
|
|
1425
|
+
|
|
1426
|
+
- **LiteLLM proxies**: May not support `store` field
|
|
1427
|
+
- **Custom inference servers**: May use non-standard field names
|
|
1428
|
+
- **Self-hosted endpoints**: May have different feature support
|
|
1429
|
+
|
|
1430
|
+
## Faux Provider for Tests
|
|
1431
|
+
|
|
1432
|
+
`fauxProvider()` builds an in-memory provider with scripted responses for tests and demos:
|
|
1433
|
+
|
|
1434
|
+
```typescript
|
|
1435
|
+
import {
|
|
1436
|
+
createModels,
|
|
1437
|
+
fauxAssistantMessage,
|
|
1438
|
+
fauxProvider,
|
|
1439
|
+
fauxText,
|
|
1440
|
+
fauxThinking,
|
|
1441
|
+
fauxToolCall,
|
|
1442
|
+
} from '@panticonic/pi-ai';
|
|
1443
|
+
|
|
1444
|
+
const faux = fauxProvider({
|
|
1445
|
+
tokensPerSecond: 50 // optional
|
|
1446
|
+
});
|
|
1447
|
+
|
|
1448
|
+
const models = createModels();
|
|
1449
|
+
models.setProvider(faux.provider);
|
|
1450
|
+
|
|
1451
|
+
const model = faux.getModel();
|
|
1452
|
+
const context = {
|
|
1453
|
+
messages: [{ role: 'user', content: 'Summarize package.json and then call echo', timestamp: Date.now() }]
|
|
1454
|
+
};
|
|
1455
|
+
|
|
1456
|
+
faux.setResponses([
|
|
1457
|
+
fauxAssistantMessage([
|
|
1458
|
+
fauxThinking('Need to inspect package metadata first.'),
|
|
1459
|
+
fauxToolCall('echo', { text: 'package.json' })
|
|
1460
|
+
], { stopReason: 'toolUse' })
|
|
1461
|
+
]);
|
|
1462
|
+
|
|
1463
|
+
const first = await models.complete(model, context, {
|
|
1464
|
+
sessionId: 'session-1',
|
|
1465
|
+
cacheRetention: 'short'
|
|
1466
|
+
});
|
|
1467
|
+
context.messages.push(first);
|
|
1468
|
+
|
|
1469
|
+
context.messages.push({
|
|
1470
|
+
role: 'toolResult',
|
|
1471
|
+
toolCallId: first.content.find((block) => block.type === 'toolCall')!.id,
|
|
1472
|
+
toolName: 'echo',
|
|
1473
|
+
content: [{ type: 'text', text: 'package.json contents here' }],
|
|
1474
|
+
isError: false,
|
|
1475
|
+
timestamp: Date.now()
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
faux.setResponses([
|
|
1479
|
+
fauxAssistantMessage([
|
|
1480
|
+
fauxThinking('Now I can summarize the tool output.'),
|
|
1481
|
+
fauxText('Here is the summary.')
|
|
1482
|
+
])
|
|
1483
|
+
]);
|
|
1484
|
+
|
|
1485
|
+
const s = models.stream(model, context);
|
|
1486
|
+
for await (const event of s) {
|
|
1487
|
+
console.log(event.type);
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
// Optional: multiple faux models for model-switching tests
|
|
1491
|
+
const multiModel = fauxProvider({
|
|
1492
|
+
provider: 'faux-multi',
|
|
1493
|
+
models: [
|
|
1494
|
+
{ id: 'faux-fast', reasoning: false },
|
|
1495
|
+
{ id: 'faux-thinker', reasoning: true }
|
|
1496
|
+
]
|
|
1497
|
+
});
|
|
1498
|
+
models.setProvider(multiModel.provider);
|
|
1499
|
+
const thinker = multiModel.getModel('faux-thinker');
|
|
1500
|
+
|
|
1501
|
+
console.log(thinker?.reasoning);
|
|
1502
|
+
console.log(faux.getPendingResponseCount());
|
|
1503
|
+
console.log(faux.state.callCount);
|
|
1504
|
+
```
|
|
1505
|
+
|
|
1506
|
+
Notes:
|
|
1507
|
+
- Responses are consumed from a queue in request start order.
|
|
1508
|
+
- If the queue is empty, the faux provider returns an assistant error message with `errorMessage: "No more faux responses queued"`.
|
|
1509
|
+
- Use `faux.setResponses([...])` to replace the remaining queue and `faux.appendResponses([...])` to add more responses.
|
|
1510
|
+
- `faux.models` exposes all faux models. `faux.getModel()` returns the first one, and `faux.getModel(id)` returns a specific one.
|
|
1511
|
+
- Use `fauxAssistantMessage(...)` for scripted assistant replies. Use `fauxText(...)`, `fauxThinking(...)`, and `fauxToolCall(...)` to build content blocks without filling in low-level fields manually.
|
|
1512
|
+
- Usage is estimated at roughly 1 token per 4 characters. When `sessionId` is present and `cacheRetention` is not `"none"`, prompt cache reads and writes are simulated automatically.
|
|
1513
|
+
- Tool call arguments stream incrementally via `toolcall_delta` chunks.
|
|
1514
|
+
- By default, each streamed chunk is emitted on its own microtask. Set `tokensPerSecond` to pace chunk delivery in real time.
|
|
1515
|
+
- The intended use is one deterministic scripted flow per handle. If you need independent concurrent flows, create separate faux providers with distinct `provider` ids.
|
|
1516
|
+
|
|
1517
|
+
## Cross-Provider Handoffs
|
|
1518
|
+
|
|
1519
|
+
The library supports seamless handoffs between different LLM providers within the same conversation. This allows you to switch models mid-conversation while preserving context, including thinking blocks, tool calls, and tool results.
|
|
1520
|
+
|
|
1521
|
+
When messages from one provider are sent to a different provider, the library automatically transforms them for compatibility:
|
|
1522
|
+
|
|
1523
|
+
- **User and tool result messages** are passed through unchanged
|
|
1524
|
+
- **Assistant messages from the same provider/API** are preserved as-is
|
|
1525
|
+
- **Assistant messages from different providers** have their thinking blocks converted to text with `<thinking>` tags
|
|
1526
|
+
- **Tool calls and regular text** are preserved unchanged
|
|
1527
|
+
|
|
1528
|
+
```typescript
|
|
1529
|
+
import { createModels, type Context } from '@panticonic/pi-ai';
|
|
1530
|
+
import { anthropicProvider } from '@panticonic/pi-ai/providers/anthropic';
|
|
1531
|
+
import { openaiProvider } from '@panticonic/pi-ai/providers/openai';
|
|
1532
|
+
import { googleProvider } from '@panticonic/pi-ai/providers/google';
|
|
1533
|
+
|
|
1534
|
+
const models = createModels();
|
|
1535
|
+
models.setProvider(anthropicProvider());
|
|
1536
|
+
models.setProvider(openaiProvider());
|
|
1537
|
+
models.setProvider(googleProvider());
|
|
1538
|
+
|
|
1539
|
+
const context: Context = { messages: [] };
|
|
1540
|
+
|
|
1541
|
+
// Start with Claude
|
|
1542
|
+
const claude = models.getModel('anthropic', 'claude-sonnet-4-5')!;
|
|
1543
|
+
context.messages.push({ role: 'user', content: 'What is 25 * 18?', timestamp: Date.now() });
|
|
1544
|
+
context.messages.push(await models.completeSimple(claude, context, { reasoning: 'medium' }));
|
|
1545
|
+
|
|
1546
|
+
// Switch to GPT-5 - it will see Claude's thinking as <thinking> tagged text
|
|
1547
|
+
const gpt5 = models.getModel('openai', 'gpt-5-mini')!;
|
|
1548
|
+
context.messages.push({ role: 'user', content: 'Is that calculation correct?', timestamp: Date.now() });
|
|
1549
|
+
context.messages.push(await models.complete(gpt5, context));
|
|
1550
|
+
|
|
1551
|
+
// Switch to Gemini
|
|
1552
|
+
const gemini = models.getModel('google', 'gemini-2.5-flash')!;
|
|
1553
|
+
context.messages.push({ role: 'user', content: 'What was the original question?', timestamp: Date.now() });
|
|
1554
|
+
const geminiResponse = await models.complete(gemini, context);
|
|
1555
|
+
```
|
|
1556
|
+
|
|
1557
|
+
All providers can handle messages from other providers — text, tool calls and results (including images), thinking blocks (transformed to tagged text), and aborted messages with partial content. This enables flexible workflows: start with a fast model, switch to a more capable one for complex reasoning, or maintain continuity across provider outages.
|
|
1558
|
+
|
|
1559
|
+
## System Messages
|
|
1560
|
+
|
|
1561
|
+
`Context.systemPrompt` and `Context.tools` are shorthand for a leading system message. The public entry points (`Models.stream()`, `streamSimple()`, `complete()`, `completeSimple()`) accept a `Context` and call `normalizeContext()` once; everything below them, including `Provider.stream()`, `ProviderStreams`, and the API implementation modules, receives the resulting `TranscriptContext`, which only has `messages`. The transcript can also carry system messages later in the conversation to change the prompt or the tool set without rewriting the history:
|
|
1562
|
+
|
|
1563
|
+
```typescript
|
|
1564
|
+
interface SystemMessage {
|
|
1565
|
+
role: "system";
|
|
1566
|
+
content: string | TextContent[]; // leading: base prompt; later: added instructions
|
|
1567
|
+
sections?: Record<string, string | null>; // named prompt sections; later messages patch by name, null removes
|
|
1568
|
+
toolsAdded?: Tool[]; // tools that become available here
|
|
1569
|
+
toolsRemoved?: ToolReference[]; // tools that stop being available here
|
|
1570
|
+
timestamp: number;
|
|
1571
|
+
}
|
|
1572
|
+
```
|
|
1573
|
+
|
|
1574
|
+
Sections are opaque text rendered verbatim after `content`, joined by blank lines. Keep each one self-delimiting (a tag, a heading) so the model can relate an update to the original. Replaying every system message in order yields the current prompt and tools; the replay helpers take the message list:
|
|
1575
|
+
|
|
1576
|
+
```typescript
|
|
1577
|
+
import { getCurrentSystemPrompt, getCurrentTools } from "@panticonic/pi-ai";
|
|
1578
|
+
|
|
1579
|
+
const messages: Message[] = [
|
|
1580
|
+
{ role: "system", content: "You are helpful.", sections: { rules: "<rules>Be brief.</rules>" }, toolsAdded: [readTool], timestamp: 1 },
|
|
1581
|
+
{ role: "user", content: "hi", timestamp: 2 },
|
|
1582
|
+
{ role: "system", content: "", sections: { rules: "<rules>Be thorough.</rules>" }, toolsRemoved: [{ name: "read" }], timestamp: 3 },
|
|
1583
|
+
];
|
|
1584
|
+
getCurrentSystemPrompt(messages); // "You are helpful.\n\n<rules>Be thorough.</rules>"
|
|
1585
|
+
getCurrentTools(messages); // []
|
|
1586
|
+
```
|
|
1587
|
+
|
|
1588
|
+
A custom `Provider` or `ProviderStreams` implementation reads the prompt and tools the same way from `context.messages`; `context.systemPrompt` and `context.tools` do not exist at that layer.
|
|
1589
|
+
|
|
1590
|
+
Models that accept system messages mid-conversation (`supportsMidConvoSystemMessages` in the model's compat settings, set by the generated catalog for verified models) receive each later system message in place, so the cached prefix stays intact; section changes are framed by name for the model. Every other model receives `collapseSystemMessages(transcript)`: the replayed prompt and current tools as the leading system message, with later system messages dropped. Anthropic models that also set `supportsMidConvoToolChanges` send tool changes as native `tool_addition`/`tool_removal` blocks: the initial tools stay active at the top level, every later declaration is sent with `defer_loading` (plus a stable deferred placeholder from the first request, which keeps Anthropic's deferred-tool scaffolding in the cached prefix), and removed tools stay declared, so tool changes do not invalidate the prompt cache. That needs at least one initial tool and no same-name redefinition; otherwise the current tool list is sent at the top level with the system text only. OpenAI Responses models with `supportsAdditionalTools` or `supportsToolSearch` anchor additive tool changes at their message; everything else sends the current tool list at the top level.
|
|
1591
|
+
|
|
1592
|
+
## Context Serialization
|
|
1593
|
+
|
|
1594
|
+
The `Context` object can be easily serialized and deserialized using standard JSON methods, making it simple to persist conversations, implement chat history, or transfer contexts between services:
|
|
1595
|
+
|
|
1596
|
+
```typescript
|
|
1597
|
+
const context: Context = {
|
|
1598
|
+
systemPrompt: 'You are a helpful assistant.',
|
|
1599
|
+
messages: [
|
|
1600
|
+
{ role: 'user', content: 'What is TypeScript?', timestamp: Date.now() }
|
|
1601
|
+
]
|
|
1602
|
+
};
|
|
1603
|
+
|
|
1604
|
+
const model = models.getModel('openai', 'gpt-4o-mini')!;
|
|
1605
|
+
const response = await models.complete(model, context);
|
|
1606
|
+
context.messages.push(response);
|
|
1607
|
+
|
|
1608
|
+
// Serialize the entire context
|
|
1609
|
+
const serialized = JSON.stringify(context);
|
|
1610
|
+
|
|
1611
|
+
// Save to database, localStorage, file, etc.
|
|
1612
|
+
localStorage.setItem('conversation', serialized);
|
|
1613
|
+
|
|
1614
|
+
// Later: deserialize and continue the conversation
|
|
1615
|
+
const restored: Context = JSON.parse(localStorage.getItem('conversation')!);
|
|
1616
|
+
restored.messages.push({ role: 'user', content: 'Tell me more about its type system', timestamp: Date.now() });
|
|
1617
|
+
|
|
1618
|
+
// Continue with any model
|
|
1619
|
+
const newModel = models.getModel('anthropic', 'claude-3-5-haiku-20241022')!;
|
|
1620
|
+
const continuation = await models.complete(newModel, restored);
|
|
1621
|
+
```
|
|
1622
|
+
|
|
1623
|
+
Models are plain serializable data too — no functions or implementations attached — so persisting "which model was this conversation using" is a `JSON.stringify` away.
|
|
1624
|
+
|
|
1625
|
+
> **Note**: If the context contains images (encoded as base64 as shown in the Image Input section), those will also be serialized.
|
|
1626
|
+
|
|
1627
|
+
## Browser Usage
|
|
1628
|
+
|
|
1629
|
+
The library supports browser environments. The core entrypoint and provider factories are side-effect free and bundle cleanly. Environment variables are not available in browsers, so pass API keys explicitly — or inject a `CredentialStore` (e.g. localStorage-backed) and let provider auth resolve from stored credentials:
|
|
1630
|
+
|
|
1631
|
+
```typescript
|
|
1632
|
+
import { createModels } from '@panticonic/pi-ai';
|
|
1633
|
+
import { anthropicProvider } from '@panticonic/pi-ai/providers/anthropic';
|
|
1634
|
+
|
|
1635
|
+
const models = createModels();
|
|
1636
|
+
models.setProvider(anthropicProvider());
|
|
1637
|
+
|
|
1638
|
+
const model = models.getModel('anthropic', 'claude-3-5-haiku-20241022')!;
|
|
1639
|
+
const response = await models.complete(model, {
|
|
1640
|
+
messages: [{ role: 'user', content: 'Hello!', timestamp: Date.now() }]
|
|
1641
|
+
}, {
|
|
1642
|
+
apiKey: 'your-api-key'
|
|
1643
|
+
});
|
|
1644
|
+
```
|
|
1645
|
+
|
|
1646
|
+
> **Security Warning**: Exposing API keys in frontend code is dangerous. Anyone can extract and abuse your keys. Only use this approach for internal tools or demos. For production applications, use a backend proxy that keeps your API keys secure.
|
|
1647
|
+
|
|
1648
|
+
Browser compatibility notes:
|
|
1649
|
+
|
|
1650
|
+
- Amazon Bedrock (`bedrock-converse-stream`) is not supported in browser environments. It can still appear in model lists; calls fail at runtime.
|
|
1651
|
+
- OAuth login flows are Node-only. They are lazy-loaded behind bundler-opaque imports, so registering an OAuth-capable provider does not pull Node-only code into a browser bundle — only actually logging in would.
|
|
1652
|
+
- Use a server-side proxy or backend service if you need Bedrock or OAuth-based auth from a web app.
|
|
1653
|
+
|
|
1654
|
+
## Bundling and Tree Shaking
|
|
1655
|
+
|
|
1656
|
+
For small bundles and low-overhead unbundled scripts, import the model runtime and only the providers you need:
|
|
1657
|
+
|
|
1658
|
+
```typescript
|
|
1659
|
+
import { createModels } from '@panticonic/pi-ai/models';
|
|
1660
|
+
import { openaiProvider } from '@panticonic/pi-ai/providers/openai';
|
|
1661
|
+
|
|
1662
|
+
const models = createModels();
|
|
1663
|
+
models.setProvider(openaiProvider());
|
|
1664
|
+
```
|
|
1665
|
+
|
|
1666
|
+
Rules:
|
|
1667
|
+
|
|
1668
|
+
- `@panticonic/pi-ai/models` exports the model runtime (`createModels`, `createProvider`, model helpers, and their types) without TypeBox, built-in catalogs, or SDK implementations. Other types can still use `import type` from the root.
|
|
1669
|
+
- `@panticonic/pi-ai` is the core entrypoint and does not import built-in catalogs, real provider factories, or SDK implementations, but it eagerly imports TypeBox and schema validation. Unbundled Node scripts do not tree-shake its unused exports; prefer `./models`, `./providers/faux`, and specific `./utils/*` subpaths when those are all you need.
|
|
1670
|
+
- `@panticonic/pi-ai/providers/<provider>` imports that provider's catalog and lazy API wrapper only.
|
|
1671
|
+
- `@panticonic/pi-ai/providers/all` imports every built-in provider factory and all catalogs. Use it only when you want the full built-in set.
|
|
1672
|
+
- With code splitting, provider SDKs stay in lazy chunks and load on first request.
|
|
1673
|
+
- Without code splitting, bundlers fold reachable lazy API implementations into the single bundle. A single-provider bundle then includes that provider's SDK; `providers/all` includes all statically visible SDKs. Bedrock is the exception: its AWS SDK implementation is loaded through a bundler-opaque Node-only import.
|
|
1674
|
+
- Importing `@panticonic/pi-ai/api/<api-id>` directly loads that API implementation and its SDK immediately.
|
|
1675
|
+
|
|
1676
|
+
Avoid `@panticonic/pi-ai/compat` in new bundled apps; it preserves the old global API and imports the full built-in catalog surface.
|
|
1677
|
+
|
|
1678
|
+
For single-file Node ESM bundles, some SDK dependencies may still use dynamic CommonJS `require()` internally. If you see errors such as `Dynamic require of "child_process" is not supported`, add a Node `require` shim to the bundle. With esbuild:
|
|
1679
|
+
|
|
1680
|
+
```bash
|
|
1681
|
+
esbuild app.js --bundle --platform=node --format=esm \
|
|
1682
|
+
--banner:js='import { createRequire } from "module";const require = createRequire(import.meta.url);' \
|
|
1683
|
+
--outfile=app.bundle.js
|
|
1684
|
+
```
|
|
1685
|
+
|
|
1686
|
+
This is only for Node bundles; it is not a browser or Cloudflare Workers workaround.
|
|
1687
|
+
|
|
1688
|
+
Bedrock is Node-only. Add it like any other provider:
|
|
1689
|
+
|
|
1690
|
+
```typescript
|
|
1691
|
+
import { createModels } from '@panticonic/pi-ai';
|
|
1692
|
+
import { amazonBedrockProvider } from '@panticonic/pi-ai/providers/amazon-bedrock';
|
|
1693
|
+
|
|
1694
|
+
const models = createModels();
|
|
1695
|
+
models.setProvider(amazonBedrockProvider());
|
|
1696
|
+
```
|
|
1697
|
+
|
|
1698
|
+
In normal Node package usage and code-split bundles, Bedrock loads its AWS SDK implementation lazily. For a standalone single-file bundle that must include Bedrock support, register the implementation module explicitly:
|
|
1699
|
+
|
|
1700
|
+
```typescript
|
|
1701
|
+
import { setBedrockProviderModule } from '@panticonic/pi-ai/api/bedrock-converse-stream.lazy';
|
|
1702
|
+
import { bedrockProviderModule } from '@panticonic/pi-ai/bedrock-provider';
|
|
1703
|
+
|
|
1704
|
+
setBedrockProviderModule(bedrockProviderModule);
|
|
1705
|
+
```
|
|
1706
|
+
|
|
1707
|
+
That explicit override bundles the AWS SDK. Without it, Bedrock's opaque runtime import expects the package's Bedrock implementation file to be available at runtime.
|
|
1708
|
+
|
|
1709
|
+
### Provider-Scoped Environment Overrides
|
|
1710
|
+
|
|
1711
|
+
Pass `env` in stream options to scope provider configuration to a request. Values in `env` are used before process environment variables for provider auth and configuration such as Cloudflare account IDs, Azure OpenAI settings, Vertex project/location, Bedrock settings, `PI_CACHE_RETENTION`, and `HTTP_PROXY`/`HTTPS_PROXY`.
|
|
1712
|
+
|
|
1713
|
+
```typescript
|
|
1714
|
+
const models = builtinModels();
|
|
1715
|
+
const model = models.getModel('cloudflare-ai-gateway', 'workers-ai/@cf/moonshotai/kimi-k2.6')!;
|
|
1716
|
+
|
|
1717
|
+
const response = await models.complete(model, context, {
|
|
1718
|
+
env: {
|
|
1719
|
+
CLOUDFLARE_API_KEY: '...',
|
|
1720
|
+
CLOUDFLARE_ACCOUNT_ID: 'account-id',
|
|
1721
|
+
CLOUDFLARE_GATEWAY_ID: 'gateway-id'
|
|
1722
|
+
}
|
|
1723
|
+
});
|
|
1724
|
+
```
|
|
1725
|
+
|
|
1726
|
+
Use this when one process needs different provider settings per request, or when ambient environment variables should not leak into a provider call.
|
|
1727
|
+
|
|
1728
|
+
## OAuth Providers
|
|
1729
|
+
|
|
1730
|
+
Several providers support OAuth authentication instead of static API keys:
|
|
1731
|
+
|
|
1732
|
+
- **Anthropic** (Claude Pro/Max subscription)
|
|
1733
|
+
- **OpenAI** (Sign in with ChatGPT: uses the ChatGPT subscription with the OpenAI API)
|
|
1734
|
+
- **OpenAI Codex (legacy)** (ChatGPT Plus/Pro subscription, access to GPT-5.x Codex models)
|
|
1735
|
+
- **GitHub Copilot** (Copilot subscription)
|
|
1736
|
+
- **OpenRouter** (OAuth PKCE that mints a user-controlled API key)
|
|
1737
|
+
|
|
1738
|
+
Each of these providers carries an `OAuthAuth` on `provider.auth.oauth` with three operations: `login(interaction)` uses the provider-neutral `AuthInteraction.prompt()`/`notify()` protocol and returns a credential, `refresh(credential, signal)` refreshes expiring credentials when applicable, and `toAuth(credential)` derives request auth (GitHub Copilot's per-account base URL comes from here). Provider login interactions and refresh calls always carry a concrete abort signal. Refresh is automatic: `models.getAuth(providerId)` and request paths refresh expired tokens under a credential-store lock, so concurrent requests and processes cannot double-refresh. OpenRouter's OAuth flow instead returns a permanent API key, so its refresh operation is a no-op.
|
|
1739
|
+
|
|
1740
|
+
```typescript
|
|
1741
|
+
import { createModels } from '@panticonic/pi-ai';
|
|
1742
|
+
import { anthropicProvider } from '@panticonic/pi-ai/providers/anthropic';
|
|
1743
|
+
|
|
1744
|
+
const models = createModels({ credentials: myStore }); // persistent CredentialStore
|
|
1745
|
+
models.setProvider(anthropicProvider());
|
|
1746
|
+
|
|
1747
|
+
// Login: Models drives the flow and persists the credential
|
|
1748
|
+
await models.login('anthropic', 'oauth', {
|
|
1749
|
+
prompt: async (p) => {
|
|
1750
|
+
// p.type: 'text' | 'secret' | 'select' | 'manual_code'
|
|
1751
|
+
// manual_code prompts race a local callback server; p.signal aborts them when the server wins
|
|
1752
|
+
return await askUser(p.message);
|
|
1753
|
+
},
|
|
1754
|
+
notify: (event) => {
|
|
1755
|
+
// event.type: 'info' | 'auth_url' | 'device_code' | 'progress'
|
|
1756
|
+
if (event.type === 'info') {
|
|
1757
|
+
console.log(event.message);
|
|
1758
|
+
for (const link of event.links ?? []) console.log(`${link.label ?? 'More information'}: ${link.url}`);
|
|
1759
|
+
}
|
|
1760
|
+
if (event.type === 'auth_url') console.log(`Open: ${event.url}`);
|
|
1761
|
+
if (event.type === 'device_code') console.log(`Code: ${event.userCode} at ${event.verificationUri}`);
|
|
1762
|
+
if (event.type === 'progress') console.log(event.message);
|
|
1763
|
+
},
|
|
1764
|
+
});
|
|
1765
|
+
|
|
1766
|
+
// From here on, requests resolve and refresh the token automatically
|
|
1767
|
+
const model = models.getModel('anthropic', 'claude-sonnet-4-5')!;
|
|
1768
|
+
await models.complete(model, context);
|
|
1769
|
+
|
|
1770
|
+
// Logout
|
|
1771
|
+
await models.logout('anthropic');
|
|
1772
|
+
```
|
|
1773
|
+
|
|
1774
|
+
### Vertex AI
|
|
1775
|
+
|
|
1776
|
+
Vertex AI models support either a Google Cloud API key or Application Default Credentials (ADC). Its provider-owned API-key login flow can configure either method:
|
|
1777
|
+
|
|
1778
|
+
- **API key**: Set `GOOGLE_CLOUD_API_KEY` or pass `apiKey` in the call options.
|
|
1779
|
+
- **Local development (ADC)**: Run `gcloud auth application-default login`
|
|
1780
|
+
- **CI/Production (ADC)**: Set `GOOGLE_APPLICATION_CREDENTIALS` to point to a service account JSON key file
|
|
1781
|
+
|
|
1782
|
+
When using ADC, also set `GOOGLE_CLOUD_PROJECT` (or `GCLOUD_PROJECT`) and `GOOGLE_CLOUD_LOCATION`. You can also pass `project`/`location` in the call options. When using `GOOGLE_CLOUD_API_KEY`, `project` and `location` are not required.
|
|
1783
|
+
|
|
1784
|
+
```bash
|
|
1785
|
+
# Local (uses your user credentials)
|
|
1786
|
+
gcloud auth application-default login
|
|
1787
|
+
export GOOGLE_CLOUD_PROJECT="my-project"
|
|
1788
|
+
export GOOGLE_CLOUD_LOCATION="us-central1"
|
|
1789
|
+
|
|
1790
|
+
# CI/Production (service account key file)
|
|
1791
|
+
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
|
|
1792
|
+
```
|
|
1793
|
+
|
|
1794
|
+
Official docs: [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials)
|
|
1795
|
+
|
|
1796
|
+
### CLI Login
|
|
1797
|
+
|
|
1798
|
+
The quickest way to authenticate:
|
|
1799
|
+
|
|
1800
|
+
```bash
|
|
1801
|
+
npx @panticonic/pi-ai login # interactive provider selection
|
|
1802
|
+
npx @panticonic/pi-ai login anthropic # login to specific provider
|
|
1803
|
+
npx @panticonic/pi-ai list # list available providers
|
|
1804
|
+
```
|
|
1805
|
+
|
|
1806
|
+
Credentials are saved to `auth.json` in the current directory.
|
|
1807
|
+
|
|
1808
|
+
### Programmatic OAuth
|
|
1809
|
+
|
|
1810
|
+
Built-in login and refresh flows are private provider implementations. Use provider-owned `OAuthAuth`, which composes with `CredentialStore` and gets locked auto-refresh through `Models`. The `@panticonic/pi-ai/oauth` entry point retains only type declarations required by coding-agent extension OAuth compatibility.
|
|
1811
|
+
|
|
1812
|
+
Provider notes:
|
|
1813
|
+
|
|
1814
|
+
**OpenAI Codex (legacy)**: Superseded by Sign in with ChatGPT on the OpenAI provider. Requires a ChatGPT Plus or Pro subscription. Provides access to GPT-5.x Codex models with extended context windows and reasoning capabilities. The library automatically handles session-based prompt caching when `sessionId` is provided in stream options unless `cacheRetention` is `"none"`. You can set `transport` in stream options to `"sse"`, `"websocket"`, or `"auto"` for Codex Responses transport selection. When using WebSocket with a `sessionId` and cache retention enabled, connections are reused per session and expire after 5 minutes of inactivity. Call `cleanupSessionResources(sessionId)` when finished so the pooled connection does not keep the process alive.
|
|
1815
|
+
|
|
1816
|
+
**Azure OpenAI (Responses)**: Uses the Responses API only. Set `AZURE_OPENAI_API_KEY` and either `AZURE_OPENAI_BASE_URL` or `AZURE_OPENAI_RESOURCE_NAME`. `AZURE_OPENAI_BASE_URL` supports both `https://<resource>.openai.azure.com` and `https://<resource>.cognitiveservices.azure.com`; root endpoints are normalized to `.../openai/v1` automatically. Use `AZURE_OPENAI_API_VERSION` (defaults to `v1`) to override the API version if needed. Deployment names are treated as model IDs by default, override with `azureDeploymentName` or `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` using comma-separated `model-id=deployment` pairs (for example `gpt-4o-mini=my-deployment,gpt-4o=prod`). Legacy deployment-based URLs are intentionally unsupported.
|
|
1817
|
+
|
|
1818
|
+
**GitHub Copilot**: If you get "The requested model is not supported" error, enable the model manually in VS Code: open Copilot Chat, click the model selector, select the model (warning icon), and click "Enable".
|
|
1819
|
+
|
|
1820
|
+
## Migrating from the Old Global API
|
|
1821
|
+
|
|
1822
|
+
Older versions exposed a global API: `stream()`/`complete()` dispatching on `model.api` via a global registry, sync `getModel()`/`getModels()`/`getProviders()` catalog reads, `registerApiProvider()`, `getEnvApiKey()`, and per-API lazy stream functions. That surface lives unchanged on the **compat entrypoint**:
|
|
1823
|
+
|
|
1824
|
+
```typescript
|
|
1825
|
+
// Before
|
|
1826
|
+
import { getModel, complete } from '@panticonic/pi-ai';
|
|
1827
|
+
|
|
1828
|
+
// After (verbatim behavior, one import-path change)
|
|
1829
|
+
import { getModel, complete } from '@panticonic/pi-ai/compat';
|
|
1830
|
+
```
|
|
1831
|
+
|
|
1832
|
+
Compat is a strict superset of the root entrypoint, so a file can switch its import path wholesale. It will be removed in a future release; migrate to `createModels()` + provider factories:
|
|
1833
|
+
|
|
1834
|
+
| Old | New |
|
|
1835
|
+
|-----|-----|
|
|
1836
|
+
| `getModel('openai', 'gpt-4o-mini')` | `models.getModel('openai', 'gpt-4o-mini')` or `getBuiltinModel()` from `providers/all` |
|
|
1837
|
+
| `getModels('anthropic')` / `getProviders()` | `models.getModels('anthropic')` / `models.getProviders()` or `getBuiltin*` |
|
|
1838
|
+
| `stream(model, ctx, opts)` (env-key injection) | `models.stream(model, ctx, opts)` (provider auth resolution) |
|
|
1839
|
+
| `registerApiProvider({ api, stream, streamSimple })` | `createProvider({ id, auth, models, api })` + `models.setProvider()` |
|
|
1840
|
+
| `getEnvApiKey('openai')` | `await models.getAuth(model.provider)` |
|
|
1841
|
+
| `streamAnthropic(model, ctx, opts)` | `stream` from `@panticonic/pi-ai/api/anthropic-messages`, or a provider in a collection |
|
|
1842
|
+
| `registerFauxProvider()` | `fauxProvider()` + `models.setProvider()` |
|
|
1843
|
+
| `getImageModel('openrouter', id)` / `generateImages(model, ctx, { apiKey })` | `models.getModelOfType('image', 'openrouter', id)` / `models.generateImages(model, ctx)` |
|
|
1844
|
+
|
|
1845
|
+
The separate `ImagesModels`/`ImagesProvider` collection that existed briefly (`createImagesModels()`, `createImagesProvider()`, `openrouterImagesProvider()`, `builtinImagesModels()`) is gone: image models now live on the regular provider. Replace `builtinImagesModels()` with `builtinModels()`, `imagesModels.getModel()` with `models.getModelOfType('image', ...)`, and `createImagesProvider({ models, api })` with `createProvider({ models, images })`. The old plural image type names are removed; use `ImageModel` and `ImageApi`, and add `type: "image"` to image model literals.
|
|
1846
|
+
|
|
1847
|
+
## Development
|
|
1848
|
+
|
|
1849
|
+
### Adding a New Provider
|
|
1850
|
+
|
|
1851
|
+
Adding a new LLM provider requires changes across multiple files. The layered layout: API implementations live in `src/api/`, provider factories in `src/providers/`, stable generated catalog wrappers live in `src/providers/<id>.models.ts`, and `src/models.generated.ts` registers them. This checklist covers all necessary steps:
|
|
1852
|
+
|
|
1853
|
+
#### 1. Core Types (`src/types.ts`)
|
|
1854
|
+
|
|
1855
|
+
- Add the API identifier to `KnownApi` (for example `"bedrock-converse-stream"`), if it is a new API
|
|
1856
|
+
- Add the provider name to `KnownProvider` (for example `"amazon-bedrock"`)
|
|
1857
|
+
- Add the options type to `ApiOptionsMap`
|
|
1858
|
+
|
|
1859
|
+
#### 2. API Implementation (`src/api/<api-id>.ts`, only for a new API)
|
|
1860
|
+
|
|
1861
|
+
Create a new API implementation file (for example `bedrock-converse-stream.ts`) that exports exactly `stream` and `streamSimple`, plus:
|
|
1862
|
+
|
|
1863
|
+
- An options interface extending `StreamOptions` (for example `BedrockOptions`)
|
|
1864
|
+
- Message conversion functions to transform the `TranscriptContext` messages to provider format; read the prompt and tools from the transcript with `getInitialSystemMessage()`, `getCurrentTools()`, and `resolveTranscript()`
|
|
1865
|
+
- Tool conversion if the provider supports tools
|
|
1866
|
+
- Response parsing to emit standardized events (`text`, `tool_call`, `thinking`, `usage`, `stop`)
|
|
1867
|
+
|
|
1868
|
+
Add a lazy wrapper `src/api/<api-id>.lazy.ts` (`<name>Api()` via `lazyApi()`) so providers can reference the implementation without importing its SDK. Add any root-level `export type` re-exports in `src/index.ts` that should remain available from `@panticonic/pi-ai`.
|
|
1869
|
+
|
|
1870
|
+
#### 3. Model Generation (`scripts/generate-models.ts`)
|
|
1871
|
+
|
|
1872
|
+
- Add logic to fetch and parse models from the provider's source (e.g., models.dev API)
|
|
1873
|
+
- Map chat/tool-capable provider data to `Model`, image-generation data to `ImageModel`, and models.dev `type: "decision"` entries to `ClassifierModel`; hydration groups the ignored `src/providers/data/<id>.json` values by API while stable `src/providers/<id>.models.ts` wrappers derive exact model/API types directly from those JSON keys
|
|
1874
|
+
- Keep model ids unique within each provider and model type; emit separate entries when an upstream model supports multiple operations
|
|
1875
|
+
- Handle provider-specific quirks (pricing format, capability flags, model ID transformations)
|
|
1876
|
+
|
|
1877
|
+
#### 4. Provider Factory (`src/providers/<id>.ts`)
|
|
1878
|
+
|
|
1879
|
+
- `createProvider()` wiring catalog + auth + the lazy API wrapper
|
|
1880
|
+
- Auth: `envApiKeyAuth` for standard key providers, a custom `ApiKeyAuth` for ambient auth (AWS profiles, ADC), `lazyOAuth` where an OAuth flow exists
|
|
1881
|
+
- Register the factory in `src/providers/all.ts`
|
|
1882
|
+
- If it is a new API: register it in the builtin list in `src/compat.ts` and add the package subpath export in `package.json`
|
|
1883
|
+
|
|
1884
|
+
#### 5. Tests (`test/`)
|
|
1885
|
+
|
|
1886
|
+
Create or update test files to cover the new provider:
|
|
1887
|
+
|
|
1888
|
+
- `stream.test.ts` - Basic streaming and tool use
|
|
1889
|
+
- `tokens.test.ts` - Token usage reporting
|
|
1890
|
+
- `abort.test.ts` - Request cancellation
|
|
1891
|
+
- `empty.test.ts` - Empty message handling
|
|
1892
|
+
- `context-overflow.test.ts` - Context limit errors
|
|
1893
|
+
- `image-limits.test.ts` - Image support (if applicable)
|
|
1894
|
+
- `unicode-surrogate.test.ts` - Unicode handling
|
|
1895
|
+
- `tool-call-without-result.test.ts` - Orphaned tool calls
|
|
1896
|
+
- `image-tool-result.test.ts` - Images in tool results
|
|
1897
|
+
- `total-tokens.test.ts` - Token counting accuracy
|
|
1898
|
+
- `cross-provider-handoff.test.ts` - Cross-provider context replay
|
|
1899
|
+
- `providers.test.ts` - Provider listing and auth resolution
|
|
1900
|
+
|
|
1901
|
+
For `cross-provider-handoff.test.ts`, add at least one provider/model pair. If the provider exposes multiple model families (for example GPT and Claude), add at least one pair per family.
|
|
1902
|
+
|
|
1903
|
+
For providers with non-standard auth (AWS, Google Vertex), create a utility like `bedrock-utils.ts` with credential detection helpers.
|
|
1904
|
+
|
|
1905
|
+
#### 6. Coding Agent Integration (`../coding-agent/`)
|
|
1906
|
+
|
|
1907
|
+
Update `src/core/model-resolver.ts`:
|
|
1908
|
+
|
|
1909
|
+
- Add a default model ID for the provider in `DEFAULT_MODELS`
|
|
1910
|
+
|
|
1911
|
+
Update `src/cli/args.ts`:
|
|
1912
|
+
|
|
1913
|
+
- Add environment variable documentation in the help text
|
|
1914
|
+
|
|
1915
|
+
Update `README.md`:
|
|
1916
|
+
|
|
1917
|
+
- Add the provider to the providers section with setup instructions
|
|
1918
|
+
|
|
1919
|
+
#### 7. Documentation
|
|
1920
|
+
|
|
1921
|
+
Update `packages/ai/README.md`:
|
|
1922
|
+
|
|
1923
|
+
- Add to the Supported Providers table
|
|
1924
|
+
- Document any provider-specific options or authentication requirements
|
|
1925
|
+
- Add environment variable to the Environment Variables section
|
|
1926
|
+
|
|
1927
|
+
#### 8. Changelog
|
|
1928
|
+
|
|
1929
|
+
Add an entry to `packages/ai/CHANGELOG.md` under `## [Unreleased]`:
|
|
1930
|
+
|
|
1931
|
+
```markdown
|
|
1932
|
+
### Added
|
|
1933
|
+
- Added support for [Provider Name] provider ([#PR](link) by [@author](link))
|
|
1934
|
+
```
|
|
1935
|
+
|
|
1936
|
+
## License
|
|
1937
|
+
|
|
1938
|
+
MIT
|