Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa012bebcb | ||
|
|
390f49b4b8 | ||
|
|
47f333299b | ||
|
|
db63eaf6ea | ||
|
|
0cf99cf5f9 | ||
|
|
d16bfe850a | ||
|
|
728eae9c25 | ||
|
|
eb84f461b8 | ||
|
|
0b3a1c2fdf | ||
|
|
1ccd14b0e1 | ||
|
|
61e7cdfbff |
@@ -199,15 +199,15 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/alibaba": "1.0.17",
|
"@ai-sdk/alibaba": "1.0.17",
|
||||||
"@ai-sdk/amazon-bedrock": "4.0.96",
|
"@ai-sdk/amazon-bedrock": "4.0.107",
|
||||||
"@ai-sdk/anthropic": "3.0.71",
|
"@ai-sdk/anthropic": "3.0.71",
|
||||||
"@ai-sdk/azure": "3.0.49",
|
"@ai-sdk/azure": "3.0.49",
|
||||||
"@ai-sdk/cerebras": "2.0.41",
|
"@ai-sdk/cerebras": "2.0.41",
|
||||||
"@ai-sdk/cohere": "3.0.27",
|
"@ai-sdk/cohere": "3.0.27",
|
||||||
"@ai-sdk/deepinfra": "2.0.41",
|
"@ai-sdk/deepinfra": "2.0.41",
|
||||||
"@ai-sdk/gateway": "3.0.104",
|
"@ai-sdk/gateway": "3.0.104",
|
||||||
"@ai-sdk/google": "3.0.63",
|
"@ai-sdk/google": "3.0.75",
|
||||||
"@ai-sdk/google-vertex": "4.0.112",
|
"@ai-sdk/google-vertex": "4.0.131",
|
||||||
"@ai-sdk/groq": "3.0.31",
|
"@ai-sdk/groq": "3.0.31",
|
||||||
"@ai-sdk/mistral": "3.0.27",
|
"@ai-sdk/mistral": "3.0.27",
|
||||||
"@ai-sdk/openai": "3.0.53",
|
"@ai-sdk/openai": "3.0.53",
|
||||||
@@ -409,15 +409,15 @@
|
|||||||
"@actions/github": "6.0.1",
|
"@actions/github": "6.0.1",
|
||||||
"@agentclientprotocol/sdk": "0.21.0",
|
"@agentclientprotocol/sdk": "0.21.0",
|
||||||
"@ai-sdk/alibaba": "1.0.17",
|
"@ai-sdk/alibaba": "1.0.17",
|
||||||
"@ai-sdk/amazon-bedrock": "4.0.96",
|
"@ai-sdk/amazon-bedrock": "4.0.107",
|
||||||
"@ai-sdk/anthropic": "3.0.71",
|
"@ai-sdk/anthropic": "3.0.71",
|
||||||
"@ai-sdk/azure": "3.0.49",
|
"@ai-sdk/azure": "3.0.49",
|
||||||
"@ai-sdk/cerebras": "2.0.41",
|
"@ai-sdk/cerebras": "2.0.41",
|
||||||
"@ai-sdk/cohere": "3.0.27",
|
"@ai-sdk/cohere": "3.0.27",
|
||||||
"@ai-sdk/deepinfra": "2.0.41",
|
"@ai-sdk/deepinfra": "2.0.41",
|
||||||
"@ai-sdk/gateway": "3.0.104",
|
"@ai-sdk/gateway": "3.0.104",
|
||||||
"@ai-sdk/google": "3.0.63",
|
"@ai-sdk/google": "3.0.75",
|
||||||
"@ai-sdk/google-vertex": "4.0.112",
|
"@ai-sdk/google-vertex": "4.0.131",
|
||||||
"@ai-sdk/groq": "3.0.31",
|
"@ai-sdk/groq": "3.0.31",
|
||||||
"@ai-sdk/mistral": "3.0.27",
|
"@ai-sdk/mistral": "3.0.27",
|
||||||
"@ai-sdk/openai": "3.0.53",
|
"@ai-sdk/openai": "3.0.53",
|
||||||
@@ -810,7 +810,7 @@
|
|||||||
|
|
||||||
"@ai-sdk/alibaba": ["@ai-sdk/alibaba@1.0.17", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZbE+U5bWz2JBc5DERLowx5+TKbjGBE93LqKZAWvuEn7HOSQMraxFMZuc0ST335QZJAyfBOzh7m1mPQ+y7EaaoA=="],
|
"@ai-sdk/alibaba": ["@ai-sdk/alibaba@1.0.17", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZbE+U5bWz2JBc5DERLowx5+TKbjGBE93LqKZAWvuEn7HOSQMraxFMZuc0ST335QZJAyfBOzh7m1mPQ+y7EaaoA=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.96", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.71", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Mc4Ias2jRMD1jOB6xWtKNPdhECeuCZyIlbr9EAGfBnyBt++sS13ziZh9qv9TdyMCAZJ7xoQcpbchoRJcKwPdpA=="],
|
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.107", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.78", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8nT08pGPy25rleJNk56ep00UHK6kCtCmu+ZNqVVSSPDieADlIZqcaN1iRXAFBoCH0Fb9F6C2EjFDaySdsargfQ=="],
|
||||||
|
|
||||||
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-rwLi/Rsuj2pYniQXIrvClHvXDzgM4UQHHnvHTWEF14efnlKclG/1ghpNC+adsRujAbCTr6gRsSbDE2vEqriV7g=="],
|
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-rwLi/Rsuj2pYniQXIrvClHvXDzgM4UQHHnvHTWEF14efnlKclG/1ghpNC+adsRujAbCTr6gRsSbDE2vEqriV7g=="],
|
||||||
|
|
||||||
@@ -832,9 +832,9 @@
|
|||||||
|
|
||||||
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.104", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZKX5n74io8VIRlhIMSLWVlvT3sXC8Z7cZ9GHuWBWZDVi96+62AIsWuLGvMfcBA1STYuSoDrp6rIziZmvrTq0TA=="],
|
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.104", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZKX5n74io8VIRlhIMSLWVlvT3sXC8Z7cZ9GHuWBWZDVi96+62AIsWuLGvMfcBA1STYuSoDrp6rIziZmvrTq0TA=="],
|
||||||
|
|
||||||
"@ai-sdk/google": ["@ai-sdk/google@3.0.63", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-RfOZWVMYSPu2sPRfGajrauWAZ9BSaRopSn+AszkKWQ1MFj8nhaXvCqRHB5pBQUaHTfZKagvOmMpNfa/s3gPLgQ=="],
|
"@ai-sdk/google": ["@ai-sdk/google@3.0.75", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XAm31ftiOrzlb8NjDzT7kw0xw+4lmgFdGFn1QKM73nXFFKyN1kWLESBV75UGNfjXP8X1YJ0YydnMVqO0jaPghw=="],
|
||||||
|
|
||||||
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.112", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.71", "@ai-sdk/google": "3.0.64", "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cSfHCkM+9ZrFtQWIN1WlV93JPD+isGSdFxKj7u1L9m2aLVZajlXdcE41GL9hMt7ld7bZYE4NnZ+4VLxBAHE+Eg=="],
|
"@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.131", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.78", "@ai-sdk/google": "3.0.75", "@ai-sdk/openai-compatible": "2.0.47", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Oj1X8p0rVgvEoR5OOSxWi6XgzJ3QDlE/n30MZVtpKkCiToYYDyvlvVDGXz3IqhMyUev2JhlcuUk1brScKT01kA=="],
|
||||||
|
|
||||||
"@ai-sdk/groq": ["@ai-sdk/groq@3.0.31", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XbbugpnFmXGu2TlXiq8KUJskP6/VVbuFcnFIGDzDIB/Chg6XHsNnqrTF80Zxkh0Pd3+NvbM+2Uqrtsndk6bDAg=="],
|
"@ai-sdk/groq": ["@ai-sdk/groq@3.0.31", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XbbugpnFmXGu2TlXiq8KUJskP6/VVbuFcnFIGDzDIB/Chg6XHsNnqrTF80Zxkh0Pd3+NvbM+2Uqrtsndk6bDAg=="],
|
||||||
|
|
||||||
@@ -5090,9 +5090,13 @@
|
|||||||
|
|
||||||
"@ai-sdk/alibaba/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
"@ai-sdk/alibaba/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.71", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bUWOzrzR0gJKJO/PLGMR4uH2dqEgqGhrsCV+sSpk4KtOEnUQlfjZI/F7BFlqSvVpFbjdgYRRLysAeEZpJ6S1lg=="],
|
"@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.78", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-0OY12G20cUt6iU6htpEA1491Oz++NVxZxlmWGX4B7rSbeZ5pnDmOu6YtW9BKzdZlNx5Gn23i6WMxyZFoMKNcgA=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.13", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.0", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-vYahwBAtRaAcFbOmE9aLr12z7RiHYDSLcnogSdxfm7kKfsNa3wH+NU5r7vTeB5rKvLsWyPjVX8iH94brP7umiQ=="],
|
"@ai-sdk/amazon-bedrock/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
||||||
|
|
||||||
|
"@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
||||||
|
|
||||||
|
"@ai-sdk/amazon-bedrock/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.14", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
"@ai-sdk/amazon-bedrock/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
||||||
|
|
||||||
@@ -5108,11 +5112,17 @@
|
|||||||
|
|
||||||
"@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
"@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
||||||
|
|
||||||
"@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.71", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bUWOzrzR0gJKJO/PLGMR4uH2dqEgqGhrsCV+sSpk4KtOEnUQlfjZI/F7BFlqSvVpFbjdgYRRLysAeEZpJ6S1lg=="],
|
"@ai-sdk/google/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
||||||
|
|
||||||
"@ai-sdk/google-vertex/@ai-sdk/google": ["@ai-sdk/google@3.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CbR82EgGPNrj/6q0HtclwuCqe0/pDShyv3nWDP/A9DroujzWXnLMlUJVrgPOsg4b40zQCwwVs2XSKCxvt/4QaA=="],
|
"@ai-sdk/google/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
||||||
|
|
||||||
"@ai-sdk/google-vertex/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
"@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.78", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-0OY12G20cUt6iU6htpEA1491Oz++NVxZxlmWGX4B7rSbeZ5pnDmOu6YtW9BKzdZlNx5Gn23i6WMxyZFoMKNcgA=="],
|
||||||
|
|
||||||
|
"@ai-sdk/google-vertex/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.47", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Enm5UlL0zUCrW3792opk5h7hRWxZOZzDe6eQYVFqX9LUOGGCe1h8MZWAGim765nwzgnjlpeYOsuzZmLtRsTPlg=="],
|
||||||
|
|
||||||
|
"@ai-sdk/google-vertex/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="],
|
||||||
|
|
||||||
|
"@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="],
|
||||||
|
|
||||||
"@ai-sdk/groq/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
"@ai-sdk/groq/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
||||||
|
|
||||||
@@ -6006,6 +6016,12 @@
|
|||||||
|
|
||||||
"@actions/github/@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
|
"@actions/github/@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
|
||||||
|
|
||||||
|
"@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils/eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="],
|
||||||
|
|
||||||
|
"@ai-sdk/amazon-bedrock/@smithy/eventstream-codec/@smithy/types": ["@smithy/types@4.14.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg=="],
|
||||||
|
|
||||||
"@ai-sdk/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
"@ai-sdk/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
"@ai-sdk/azure/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
"@ai-sdk/azure/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
@@ -6016,6 +6032,14 @@
|
|||||||
|
|
||||||
"@ai-sdk/deepinfra/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
"@ai-sdk/deepinfra/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"@ai-sdk/google-vertex/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"@ai-sdk/google-vertex/@ai-sdk/provider-utils/eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="],
|
||||||
|
|
||||||
|
"@ai-sdk/google/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"@ai-sdk/google/@ai-sdk/provider-utils/eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="],
|
||||||
|
|
||||||
"@ai-sdk/groq/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
"@ai-sdk/groq/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
"@ai-sdk/mistral/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
"@ai-sdk/mistral/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"nodeModules": {
|
"nodeModules": {
|
||||||
"x86_64-linux": "sha256-qq8cKNcW10NRbPx2T/Gpzby4nx2qHzj0LWDwqyPl7rY=",
|
"x86_64-linux": "sha256-szOnLhAI4d3O4NaQvCRBufklmtRU8AR8p5LyFxjlTXo=",
|
||||||
"aarch64-linux": "sha256-UguK4nObAL/ovGZQousveoXCmkcxEgWy7XSSzxVJNEY=",
|
"aarch64-linux": "sha256-mfyL4Pswl6HeObPg+TMK1r7RjvLCMgDWYrFnfHkXGj4=",
|
||||||
"aarch64-darwin": "sha256-NibSqZiF0XMbFhOtl2CQvFe4N/BXB5UAdt2iKv7dIcI=",
|
"aarch64-darwin": "sha256-XMK5IFCIxlSwH21Nqya8ZkAQ4Skp6GghukIl7NMp+so=",
|
||||||
"x86_64-darwin": "sha256-ZNeZSpYitdUjXx/sULAIoLXrM6+Vz+GXQxKpdfgXh60="
|
"x86_64-darwin": "sha256-W7nLMO6qhR32442yMebEJJgaZRNpqTbZ8BJhdqMK3S4="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,15 +27,15 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/alibaba": "1.0.17",
|
"@ai-sdk/alibaba": "1.0.17",
|
||||||
"@ai-sdk/amazon-bedrock": "4.0.96",
|
"@ai-sdk/amazon-bedrock": "4.0.107",
|
||||||
"@ai-sdk/anthropic": "3.0.71",
|
"@ai-sdk/anthropic": "3.0.71",
|
||||||
"@ai-sdk/azure": "3.0.49",
|
"@ai-sdk/azure": "3.0.49",
|
||||||
"@ai-sdk/cerebras": "2.0.41",
|
"@ai-sdk/cerebras": "2.0.41",
|
||||||
"@ai-sdk/cohere": "3.0.27",
|
"@ai-sdk/cohere": "3.0.27",
|
||||||
"@ai-sdk/deepinfra": "2.0.41",
|
"@ai-sdk/deepinfra": "2.0.41",
|
||||||
"@ai-sdk/gateway": "3.0.104",
|
"@ai-sdk/gateway": "3.0.104",
|
||||||
"@ai-sdk/google": "3.0.63",
|
"@ai-sdk/google": "3.0.75",
|
||||||
"@ai-sdk/google-vertex": "4.0.112",
|
"@ai-sdk/google-vertex": "4.0.131",
|
||||||
"@ai-sdk/groq": "3.0.31",
|
"@ai-sdk/groq": "3.0.31",
|
||||||
"@ai-sdk/mistral": "3.0.27",
|
"@ai-sdk/mistral": "3.0.27",
|
||||||
"@ai-sdk/openai": "3.0.53",
|
"@ai-sdk/openai": "3.0.53",
|
||||||
|
|||||||
+6
-1
@@ -15,6 +15,7 @@ type ServiceUse<Identifier, Shape> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const serviceUse = <Identifier, Shape>(tag: Context.Service<Identifier, Shape>) => {
|
export const serviceUse = <Identifier, Shape>(tag: Context.Service<Identifier, Shape>) => {
|
||||||
|
const cache = new Map<string, (...args: unknown[]) => Effect.Effect<unknown, unknown, unknown>>()
|
||||||
// This is the only dynamic boundary: TypeScript knows the accessor shape,
|
// This is the only dynamic boundary: TypeScript knows the accessor shape,
|
||||||
// but Proxy property names are runtime values.
|
// but Proxy property names are runtime values.
|
||||||
const access = new Proxy(
|
const access = new Proxy(
|
||||||
@@ -22,7 +23,9 @@ export const serviceUse = <Identifier, Shape>(tag: Context.Service<Identifier, S
|
|||||||
{
|
{
|
||||||
get: (_, key) => {
|
get: (_, key) => {
|
||||||
if (typeof key !== "string") return undefined
|
if (typeof key !== "string") return undefined
|
||||||
return (...args: unknown[]) =>
|
const cached = cache.get(key)
|
||||||
|
if (cached) return cached
|
||||||
|
const accessor = (...args: unknown[]) =>
|
||||||
tag.use((service) => {
|
tag.use((service) => {
|
||||||
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- Proxy keys are checked at runtime.
|
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- Proxy keys are checked at runtime.
|
||||||
const method = service[key as keyof Shape]
|
const method = service[key as keyof Shape]
|
||||||
@@ -30,6 +33,8 @@ export const serviceUse = <Identifier, Shape>(tag: Context.Service<Identifier, S
|
|||||||
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- ServiceUse exposes only Effect-returning methods.
|
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- ServiceUse exposes only Effect-returning methods.
|
||||||
return (method as (...args: unknown[]) => Effect.Effect<unknown, unknown, unknown>)(...args)
|
return (method as (...args: unknown[]) => Effect.Effect<unknown, unknown, unknown>)(...args)
|
||||||
})
|
})
|
||||||
|
cache.set(key, accessor)
|
||||||
|
return accessor
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -3,9 +3,10 @@ import { dirname, join, relative, resolve as pathResolve } from "path"
|
|||||||
import { realpathSync } from "fs"
|
import { realpathSync } from "fs"
|
||||||
import * as NFS from "fs/promises"
|
import * as NFS from "fs/promises"
|
||||||
import { lookup } from "mime-types"
|
import { lookup } from "mime-types"
|
||||||
import { Effect, FileSystem, Layer, Schema, Context } from "effect"
|
import { Context, Effect, FileSystem, Layer, Schema } from "effect"
|
||||||
import type { PlatformError } from "effect/PlatformError"
|
import type { PlatformError } from "effect/PlatformError"
|
||||||
import { Glob } from "./util/glob"
|
import { Glob } from "./util/glob"
|
||||||
|
import { serviceUse } from "./effect/service-use"
|
||||||
|
|
||||||
export namespace AppFileSystem {
|
export namespace AppFileSystem {
|
||||||
export class FileSystemError extends Schema.TaggedErrorClass<FileSystemError>()("FileSystemError", {
|
export class FileSystemError extends Schema.TaggedErrorClass<FileSystemError>()("FileSystemError", {
|
||||||
@@ -39,6 +40,8 @@ export namespace AppFileSystem {
|
|||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/FileSystem") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/FileSystem") {}
|
||||||
|
|
||||||
|
export const use = serviceUse(Service)
|
||||||
|
|
||||||
export const layer = Layer.effect(
|
export const layer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export const layer = Layer.effect(
|
|||||||
if (!repo) return { id: ID.global, directory: input, vcs: undefined }
|
if (!repo) return { id: ID.global, directory: input, vcs: undefined }
|
||||||
|
|
||||||
const previous = yield* cached(repo.store)
|
const previous = yield* cached(repo.store)
|
||||||
const id = (yield* remote(repo)) ?? previous ?? (yield* root(repo))
|
const id = previous ?? (yield* root(repo))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
previous,
|
previous,
|
||||||
|
|||||||
@@ -5,16 +5,11 @@ import path from "path"
|
|||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import { Project } from "@opencode-ai/core/project"
|
import { Project } from "@opencode-ai/core/project"
|
||||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||||
import { Hash } from "@opencode-ai/core/util/hash"
|
|
||||||
import { tmpdir } from "./fixture/tmpdir"
|
import { tmpdir } from "./fixture/tmpdir"
|
||||||
import { testEffect } from "./lib/effect"
|
import { testEffect } from "./lib/effect"
|
||||||
|
|
||||||
const it = testEffect(Project.defaultLayer)
|
const it = testEffect(Project.defaultLayer)
|
||||||
|
|
||||||
function remoteID(remote: string) {
|
|
||||||
return Project.ID.make(Hash.fast(`git-remote:${remote}`))
|
|
||||||
}
|
|
||||||
|
|
||||||
function abs(value: string) {
|
function abs(value: string) {
|
||||||
return AbsolutePath.make(value)
|
return AbsolutePath.make(value)
|
||||||
}
|
}
|
||||||
@@ -91,7 +86,7 @@ describe("ProjectV2.resolve", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.live("prefers normalized origin over root commit", () =>
|
it.live("uses root commit when origin exists", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const tmp = yield* Effect.acquireRelease(
|
const tmp = yield* Effect.acquireRelease(
|
||||||
Effect.promise(() => tmpdir()),
|
Effect.promise(() => tmpdir()),
|
||||||
@@ -102,36 +97,13 @@ describe("ProjectV2.resolve", () => {
|
|||||||
|
|
||||||
const result = yield* project.resolve(abs(tmp.path))
|
const result = yield* project.resolve(abs(tmp.path))
|
||||||
|
|
||||||
expect(result.id).toBe(remoteID("github.com/Acme/App"))
|
expect(result.id).toBe(Project.ID.make(yield* Effect.promise(() => rootCommit(tmp.path))))
|
||||||
expect(result.id).not.toBe(Project.ID.make(yield* Effect.promise(() => rootCommit(tmp.path))))
|
|
||||||
expect(result.directory).toBe(yield* real(tmp.path))
|
expect(result.directory).toBe(yield* real(tmp.path))
|
||||||
expect(result.vcs?.type).toBe("git")
|
expect(result.vcs?.type).toBe("git")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.live("normalizes ssh and https remotes to the same id", () =>
|
it.live("uses root commit when local remote exists", () =>
|
||||||
Effect.gen(function* () {
|
|
||||||
const ssh = yield* Effect.acquireRelease(
|
|
||||||
Effect.promise(() => tmpdir()),
|
|
||||||
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
|
||||||
)
|
|
||||||
const https = yield* Effect.acquireRelease(
|
|
||||||
Effect.promise(() => tmpdir()),
|
|
||||||
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
|
||||||
)
|
|
||||||
yield* Effect.promise(() => initRepo(ssh.path, { commit: true, remote: "git@github.com:owner/repo.git" }))
|
|
||||||
yield* Effect.promise(() => initRepo(https.path, { commit: true, remote: "https://github.com/owner/repo.git" }))
|
|
||||||
const project = yield* Project.Service
|
|
||||||
|
|
||||||
const a = yield* project.resolve(abs(ssh.path))
|
|
||||||
const b = yield* project.resolve(abs(https.path))
|
|
||||||
|
|
||||||
expect(a.id).toBe(remoteID("github.com/owner/repo"))
|
|
||||||
expect(b.id).toBe(a.id)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.live("ignores file remotes and falls back to root commit", () =>
|
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const tmp = yield* Effect.acquireRelease(
|
const tmp = yield* Effect.acquireRelease(
|
||||||
Effect.promise(() => tmpdir()),
|
Effect.promise(() => tmpdir()),
|
||||||
@@ -146,7 +118,7 @@ describe("ProjectV2.resolve", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.live("returns previous cached id from common dir", () =>
|
it.live("prefers previous cached id over origin", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const tmp = yield* Effect.acquireRelease(
|
const tmp = yield* Effect.acquireRelease(
|
||||||
Effect.promise(() => tmpdir()),
|
Effect.promise(() => tmpdir()),
|
||||||
@@ -159,7 +131,7 @@ describe("ProjectV2.resolve", () => {
|
|||||||
const result = yield* project.resolve(abs(tmp.path))
|
const result = yield* project.resolve(abs(tmp.path))
|
||||||
|
|
||||||
expect(result.previous).toBe(Project.ID.make("old-id"))
|
expect(result.previous).toBe(Project.ID.make("old-id"))
|
||||||
expect(result.id).toBe(remoteID("github.com/owner/repo"))
|
expect(result.id).toBe(Project.ID.make("old-id"))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -213,7 +185,7 @@ describe("ProjectV2.resolve", () => {
|
|||||||
|
|
||||||
expect(result.directory).toBe(yield* real(worktree))
|
expect(result.directory).toBe(yield* real(worktree))
|
||||||
expect(result.previous).toBe(Project.ID.make("old-id"))
|
expect(result.previous).toBe(Project.ID.make("old-id"))
|
||||||
expect(result.id).toBe(remoteID("github.com/owner/repo"))
|
expect(result.id).toBe(Project.ID.make("old-id"))
|
||||||
expect(result.vcs?.type).toBe("git")
|
expect(result.vcs?.type).toBe("git")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared"
|
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared"
|
||||||
import * as Cache from "./utils/cache"
|
import * as Cache from "./utils/cache"
|
||||||
import { Lifecycle } from "./utils/lifecycle"
|
import { Lifecycle } from "./utils/lifecycle"
|
||||||
import { ProviderOptions } from "./utils/provider-options"
|
|
||||||
import { ToolStream } from "./utils/tool-stream"
|
import { ToolStream } from "./utils/tool-stream"
|
||||||
|
|
||||||
const ADAPTER = "anthropic-messages"
|
const ADAPTER = "anthropic-messages"
|
||||||
@@ -137,7 +136,6 @@ const AnthropicTool = Schema.Struct({
|
|||||||
description: Schema.String,
|
description: Schema.String,
|
||||||
input_schema: JsonObject,
|
input_schema: JsonObject,
|
||||||
cache_control: Schema.optional(AnthropicCacheControl),
|
cache_control: Schema.optional(AnthropicCacheControl),
|
||||||
eager_input_streaming: Schema.optional(Schema.Boolean),
|
|
||||||
})
|
})
|
||||||
type AnthropicTool = Schema.Schema.Type<typeof AnthropicTool>
|
type AnthropicTool = Schema.Schema.Type<typeof AnthropicTool>
|
||||||
|
|
||||||
@@ -146,10 +144,10 @@ const AnthropicToolChoice = Schema.Union([
|
|||||||
Schema.Struct({ type: Schema.tag("tool"), name: Schema.String }),
|
Schema.Struct({ type: Schema.tag("tool"), name: Schema.String }),
|
||||||
])
|
])
|
||||||
|
|
||||||
// Anthropic accepts several `thinking` shapes (enabled with `budget_tokens`,
|
const AnthropicThinking = Schema.Struct({
|
||||||
// adaptive with optional `display`, and disabled). The body schema permits the
|
type: Schema.tag("enabled"),
|
||||||
// full union so explicit lowering can pick the correct fields per model.
|
budget_tokens: Schema.Number,
|
||||||
const AnthropicThinkingBody = Schema.Record(Schema.String, Schema.Unknown)
|
})
|
||||||
|
|
||||||
const AnthropicBodyFields = {
|
const AnthropicBodyFields = {
|
||||||
model: Schema.String,
|
model: Schema.String,
|
||||||
@@ -163,12 +161,9 @@ const AnthropicBodyFields = {
|
|||||||
top_p: Schema.optional(Schema.Number),
|
top_p: Schema.optional(Schema.Number),
|
||||||
top_k: Schema.optional(Schema.Number),
|
top_k: Schema.optional(Schema.Number),
|
||||||
stop_sequences: optionalArray(Schema.String),
|
stop_sequences: optionalArray(Schema.String),
|
||||||
thinking: Schema.optional(AnthropicThinkingBody),
|
thinking: Schema.optional(AnthropicThinking),
|
||||||
}
|
}
|
||||||
// Unknown provider options pass through verbatim with top-level keys snake-cased.
|
const AnthropicMessagesBody = Schema.Struct(AnthropicBodyFields)
|
||||||
const AnthropicMessagesBody = Schema.StructWithRest(Schema.Struct(AnthropicBodyFields), [
|
|
||||||
Schema.Record(Schema.String, Schema.Any),
|
|
||||||
])
|
|
||||||
export type AnthropicMessagesBody = Schema.Schema.Type<typeof AnthropicMessagesBody>
|
export type AnthropicMessagesBody = Schema.Schema.Type<typeof AnthropicMessagesBody>
|
||||||
|
|
||||||
const AnthropicUsage = Schema.Struct({
|
const AnthropicUsage = Schema.Struct({
|
||||||
@@ -259,16 +254,11 @@ const signatureFromMetadata = (metadata: ProviderMetadata | undefined): string |
|
|||||||
return typeof anthropic.signature === "string" ? anthropic.signature : undefined
|
return typeof anthropic.signature === "string" ? anthropic.signature : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const lowerTool = (
|
const lowerTool = (breakpoints: Cache.Breakpoints, tool: ToolDefinition): AnthropicTool => ({
|
||||||
breakpoints: Cache.Breakpoints,
|
|
||||||
tool: ToolDefinition,
|
|
||||||
eagerInputStreaming: boolean | undefined,
|
|
||||||
): AnthropicTool => ({
|
|
||||||
name: tool.name,
|
name: tool.name,
|
||||||
description: tool.description,
|
description: tool.description,
|
||||||
input_schema: tool.inputSchema,
|
input_schema: tool.inputSchema,
|
||||||
cache_control: cacheControl(breakpoints, tool.cache),
|
cache_control: cacheControl(breakpoints, tool.cache),
|
||||||
eager_input_streaming: eagerInputStreaming ? true : undefined,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const lowerToolChoice = (toolChoice: NonNullable<LLMRequest["toolChoice"]>) =>
|
const lowerToolChoice = (toolChoice: NonNullable<LLMRequest["toolChoice"]>) =>
|
||||||
@@ -423,46 +413,24 @@ const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (
|
|||||||
return messages
|
return messages
|
||||||
})
|
})
|
||||||
|
|
||||||
// Typed AI SDK Anthropic options. Mirrors the subset of
|
const anthropicOptions = (request: LLMRequest) => request.providerOptions?.anthropic
|
||||||
// `anthropicLanguageModelOptions` that opencode's provider transform actually
|
|
||||||
// emits today (see `packages/opencode/src/provider/transform.ts`). Unknown
|
|
||||||
// keys flow through the index signature and pass through to the wire body
|
|
||||||
// with their top-level key snake-cased.
|
|
||||||
type AnthropicEffort = "low" | "medium" | "high" | "xhigh" | "max"
|
|
||||||
type AnthropicThinking =
|
|
||||||
| { readonly type: "enabled"; readonly budgetTokens?: number; readonly budget_tokens?: number }
|
|
||||||
| { readonly type: "adaptive"; readonly display?: "omitted" | "summarized" }
|
|
||||||
| { readonly type: "disabled" }
|
|
||||||
|
|
||||||
interface AnthropicOptions {
|
const lowerThinking = Effect.fn("AnthropicMessages.lowerThinking")(function* (request: LLMRequest) {
|
||||||
readonly thinking?: AnthropicThinking
|
const thinking = anthropicOptions(request)?.thinking
|
||||||
readonly effort?: AnthropicEffort
|
if (!ProviderShared.isRecord(thinking) || thinking.type !== "enabled") return undefined
|
||||||
readonly toolStreaming?: boolean
|
const budget =
|
||||||
readonly [extra: string]: unknown
|
typeof thinking.budgetTokens === "number"
|
||||||
}
|
? thinking.budgetTokens
|
||||||
|
: typeof thinking.budget_tokens === "number"
|
||||||
const ANTHROPIC_KNOWN_KEYS: ReadonlySet<string> = new Set(["thinking", "effort", "toolStreaming"])
|
? thinking.budget_tokens
|
||||||
|
: undefined
|
||||||
const lowerThinking = (thinking: AnthropicOptions["thinking"]) => {
|
if (budget === undefined) return yield* invalid("Anthropic thinking provider option requires budgetTokens")
|
||||||
if (thinking === undefined) return undefined
|
|
||||||
if (thinking.type === "disabled") return undefined
|
|
||||||
if (thinking.type === "adaptive") {
|
|
||||||
return { type: "adaptive" as const, ...(thinking.display ? { display: thinking.display } : {}) }
|
|
||||||
}
|
|
||||||
const budget = thinking.budgetTokens ?? thinking.budget_tokens
|
|
||||||
if (budget === undefined) return undefined
|
|
||||||
return { type: "enabled" as const, budget_tokens: budget }
|
return { type: "enabled" as const, budget_tokens: budget }
|
||||||
}
|
})
|
||||||
|
|
||||||
const fromRequest = Effect.fn("AnthropicMessages.fromRequest")(function* (request: LLMRequest) {
|
const fromRequest = Effect.fn("AnthropicMessages.fromRequest")(function* (request: LLMRequest) {
|
||||||
const toolChoice = request.toolChoice ? yield* lowerToolChoice(request.toolChoice) : undefined
|
const toolChoice = request.toolChoice ? yield* lowerToolChoice(request.toolChoice) : undefined
|
||||||
const generation = request.generation
|
const generation = request.generation
|
||||||
const options = ProviderOptions.merge(request, ["anthropic"]) as AnthropicOptions
|
|
||||||
// AI SDK's `toolStreaming` controls per-tool `eager_input_streaming`. opencode
|
|
||||||
// sets `toolStreaming: false` for non-Claude models routed through
|
|
||||||
// `@ai-sdk/anthropic`; otherwise the field is left unset so the provider
|
|
||||||
// applies its own default.
|
|
||||||
const eagerInputStreaming = options.toolStreaming === true
|
|
||||||
// Allocate the 4-breakpoint budget in invalidation order: tools → system →
|
// Allocate the 4-breakpoint budget in invalidation order: tools → system →
|
||||||
// messages. Tools live highest in the cache hierarchy, so when callers
|
// messages. Tools live highest in the cache hierarchy, so when callers
|
||||||
// over-mark we keep their tool hints and shed the message-tail ones first.
|
// over-mark we keep their tool hints and shed the message-tail ones first.
|
||||||
@@ -470,7 +438,7 @@ const fromRequest = Effect.fn("AnthropicMessages.fromRequest")(function* (reques
|
|||||||
const tools =
|
const tools =
|
||||||
request.tools.length === 0 || request.toolChoice?.type === "none"
|
request.tools.length === 0 || request.toolChoice?.type === "none"
|
||||||
? undefined
|
? undefined
|
||||||
: request.tools.map((tool) => lowerTool(breakpoints, tool, eagerInputStreaming))
|
: request.tools.map((tool) => lowerTool(breakpoints, tool))
|
||||||
const system =
|
const system =
|
||||||
request.system.length === 0
|
request.system.length === 0
|
||||||
? undefined
|
? undefined
|
||||||
@@ -486,7 +454,6 @@ const fromRequest = Effect.fn("AnthropicMessages.fromRequest")(function* (reques
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...ProviderOptions.passthrough(options, ANTHROPIC_KNOWN_KEYS),
|
|
||||||
model: request.model.id,
|
model: request.model.id,
|
||||||
system,
|
system,
|
||||||
messages,
|
messages,
|
||||||
@@ -498,8 +465,7 @@ const fromRequest = Effect.fn("AnthropicMessages.fromRequest")(function* (reques
|
|||||||
top_p: generation?.topP,
|
top_p: generation?.topP,
|
||||||
top_k: generation?.topK,
|
top_k: generation?.topK,
|
||||||
stop_sequences: generation?.stop,
|
stop_sequences: generation?.stop,
|
||||||
thinking: lowerThinking(options.thinking),
|
thinking: yield* lowerThinking(request),
|
||||||
...(options.effort !== undefined ? { effort: options.effort } : {}),
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
} from "../schema"
|
} from "../schema"
|
||||||
import { isRecord, JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared"
|
import { isRecord, JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared"
|
||||||
import { OpenAIOptions } from "./utils/openai-options"
|
import { OpenAIOptions } from "./utils/openai-options"
|
||||||
import { ProviderOptions } from "./utils/provider-options"
|
|
||||||
import { Lifecycle } from "./utils/lifecycle"
|
import { Lifecycle } from "./utils/lifecycle"
|
||||||
import { ToolStream } from "./utils/tool-stream"
|
import { ToolStream } from "./utils/tool-stream"
|
||||||
|
|
||||||
@@ -51,10 +50,6 @@ const OpenAIChatAssistantToolCall = Schema.Struct({
|
|||||||
})
|
})
|
||||||
type OpenAIChatAssistantToolCall = Schema.Schema.Type<typeof OpenAIChatAssistantToolCall>
|
type OpenAIChatAssistantToolCall = Schema.Schema.Type<typeof OpenAIChatAssistantToolCall>
|
||||||
|
|
||||||
// `reasoning_content` is a plain string per DeepSeek/OpenAI-compatible spec.
|
|
||||||
// `reasoning_details` is an OpenRouter-style array of typed reasoning objects
|
|
||||||
// (summary / encrypted / text). We accept the structured payload as-is so it
|
|
||||||
// round-trips verbatim to the provider on continuation requests.
|
|
||||||
const OpenAIChatMessage = Schema.Union([
|
const OpenAIChatMessage = Schema.Union([
|
||||||
Schema.Struct({ role: Schema.Literal("system"), content: Schema.String }),
|
Schema.Struct({ role: Schema.Literal("system"), content: Schema.String }),
|
||||||
Schema.Struct({ role: Schema.Literal("user"), content: Schema.String }),
|
Schema.Struct({ role: Schema.Literal("user"), content: Schema.String }),
|
||||||
@@ -63,7 +58,6 @@ const OpenAIChatMessage = Schema.Union([
|
|||||||
content: Schema.NullOr(Schema.String),
|
content: Schema.NullOr(Schema.String),
|
||||||
tool_calls: optionalArray(OpenAIChatAssistantToolCall),
|
tool_calls: optionalArray(OpenAIChatAssistantToolCall),
|
||||||
reasoning_content: Schema.optional(Schema.String),
|
reasoning_content: Schema.optional(Schema.String),
|
||||||
reasoning_details: Schema.optional(Schema.Array(Schema.Record(Schema.String, Schema.Unknown))),
|
|
||||||
}),
|
}),
|
||||||
Schema.Struct({ role: Schema.Literal("tool"), tool_call_id: Schema.String, content: Schema.String }),
|
Schema.Struct({ role: Schema.Literal("tool"), tool_call_id: Schema.String, content: Schema.String }),
|
||||||
]).pipe(Schema.toTaggedUnion("role"))
|
]).pipe(Schema.toTaggedUnion("role"))
|
||||||
@@ -85,7 +79,7 @@ export const bodyFields = {
|
|||||||
stream: Schema.Literal(true),
|
stream: Schema.Literal(true),
|
||||||
stream_options: Schema.optional(Schema.Struct({ include_usage: Schema.Boolean })),
|
stream_options: Schema.optional(Schema.Struct({ include_usage: Schema.Boolean })),
|
||||||
store: Schema.optional(Schema.Boolean),
|
store: Schema.optional(Schema.Boolean),
|
||||||
reasoning_effort: Schema.optional(Schema.String),
|
reasoning_effort: Schema.optional(OpenAIOptions.OpenAIReasoningEffort),
|
||||||
max_tokens: Schema.optional(Schema.Number),
|
max_tokens: Schema.optional(Schema.Number),
|
||||||
temperature: Schema.optional(Schema.Number),
|
temperature: Schema.optional(Schema.Number),
|
||||||
top_p: Schema.optional(Schema.Number),
|
top_p: Schema.optional(Schema.Number),
|
||||||
@@ -94,7 +88,7 @@ export const bodyFields = {
|
|||||||
seed: Schema.optional(Schema.Number),
|
seed: Schema.optional(Schema.Number),
|
||||||
stop: optionalArray(Schema.String),
|
stop: optionalArray(Schema.String),
|
||||||
}
|
}
|
||||||
const OpenAIChatBody = Schema.StructWithRest(Schema.Struct(bodyFields), [Schema.Record(Schema.String, Schema.Any)])
|
const OpenAIChatBody = Schema.Struct(bodyFields)
|
||||||
export type OpenAIChatBody = Schema.Schema.Type<typeof OpenAIChatBody>
|
export type OpenAIChatBody = Schema.Schema.Type<typeof OpenAIChatBody>
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -131,16 +125,9 @@ const OpenAIChatToolCallDelta = Schema.Struct({
|
|||||||
})
|
})
|
||||||
type OpenAIChatToolCallDelta = Schema.Schema.Type<typeof OpenAIChatToolCallDelta>
|
type OpenAIChatToolCallDelta = Schema.Schema.Type<typeof OpenAIChatToolCallDelta>
|
||||||
|
|
||||||
// Streaming reasoning fields. `reasoning_content` (DeepSeek) and `reasoning`
|
|
||||||
// (AI SDK fallback) are strings; `reasoning_details` (OpenRouter) is an array
|
|
||||||
// of typed reasoning detail objects. We surface their plaintext via reasoning
|
|
||||||
// deltas and preserve the structured array for downstream round-trip.
|
|
||||||
const OpenAIChatReasoningDetail = Schema.Record(Schema.String, Schema.Unknown)
|
|
||||||
const OpenAIChatDelta = Schema.Struct({
|
const OpenAIChatDelta = Schema.Struct({
|
||||||
content: optionalNull(Schema.String),
|
content: optionalNull(Schema.String),
|
||||||
reasoning_content: optionalNull(Schema.String),
|
reasoning_content: optionalNull(Schema.String),
|
||||||
reasoning: optionalNull(Schema.String),
|
|
||||||
reasoning_details: optionalNull(Schema.Array(OpenAIChatReasoningDetail)),
|
|
||||||
tool_calls: optionalNull(Schema.Array(OpenAIChatToolCallDelta)),
|
tool_calls: optionalNull(Schema.Array(OpenAIChatToolCallDelta)),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -201,16 +188,6 @@ const lowerToolCall = (part: ToolCallPart): OpenAIChatAssistantToolCall => ({
|
|||||||
const openAICompatibleReasoningContent = (native: unknown) =>
|
const openAICompatibleReasoningContent = (native: unknown) =>
|
||||||
isRecord(native) && typeof native.reasoning_content === "string" ? native.reasoning_content : undefined
|
isRecord(native) && typeof native.reasoning_content === "string" ? native.reasoning_content : undefined
|
||||||
|
|
||||||
// `reasoning_details` rounds-trips the OpenRouter structured array. Accept the
|
|
||||||
// array shape as canonical; tolerate a string for legacy callers that already
|
|
||||||
// flattened it.
|
|
||||||
const openAICompatibleReasoningDetails = (native: unknown) => {
|
|
||||||
if (!isRecord(native)) return undefined
|
|
||||||
const value = native.reasoning_details
|
|
||||||
if (Array.isArray(value)) return value as ReadonlyArray<Record<string, unknown>>
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
const lowerUserMessage = Effect.fn("OpenAIChat.lowerUserMessage")(function* (message: OpenAIChatRequestMessage) {
|
const lowerUserMessage = Effect.fn("OpenAIChat.lowerUserMessage")(function* (message: OpenAIChatRequestMessage) {
|
||||||
const content: TextPart[] = []
|
const content: TextPart[] = []
|
||||||
for (const part of message.content) {
|
for (const part of message.content) {
|
||||||
@@ -243,7 +220,6 @@ const lowerAssistantMessage = Effect.fn("OpenAIChat.lowerAssistantMessage")(func
|
|||||||
content: content.length === 0 ? null : ProviderShared.joinText(content),
|
content: content.length === 0 ? null : ProviderShared.joinText(content),
|
||||||
tool_calls: toolCalls.length === 0 ? undefined : toolCalls,
|
tool_calls: toolCalls.length === 0 ? undefined : toolCalls,
|
||||||
reasoning_content: openAICompatibleReasoningContent(message.native?.openaiCompatible),
|
reasoning_content: openAICompatibleReasoningContent(message.native?.openaiCompatible),
|
||||||
reasoning_details: openAICompatibleReasoningDetails(message.native?.openaiCompatible),
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -270,14 +246,13 @@ const lowerMessages = Effect.fn("OpenAIChat.lowerMessages")(function* (request:
|
|||||||
})
|
})
|
||||||
|
|
||||||
const lowerOptions = Effect.fn("OpenAIChat.lowerOptions")(function* (request: LLMRequest) {
|
const lowerOptions = Effect.fn("OpenAIChat.lowerOptions")(function* (request: LLMRequest) {
|
||||||
const options = OpenAIOptions.options(request)
|
const store = OpenAIOptions.store(request)
|
||||||
const effort = options.reasoningEffort
|
const reasoningEffort = OpenAIOptions.reasoningEffort(request)
|
||||||
if (effort !== undefined && !OpenAIOptions.isReasoningEffort(effort))
|
if (reasoningEffort && !OpenAIOptions.isReasoningEffort(reasoningEffort))
|
||||||
return yield* invalid(`OpenAI Chat does not support reasoning effort ${effort}`)
|
return yield* invalid(`OpenAI Chat does not support reasoning effort ${reasoningEffort}`)
|
||||||
return {
|
return {
|
||||||
...ProviderOptions.passthrough(options, OpenAIOptions.KNOWN_KEYS),
|
...(store !== undefined ? { store } : {}),
|
||||||
...(options.store !== undefined ? { store: options.store } : {}),
|
...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),
|
||||||
...(effort !== undefined ? { reasoning_effort: effort } : {}),
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -350,15 +325,8 @@ const step = (state: ParserState, event: OpenAIChatEvent) =>
|
|||||||
|
|
||||||
let lifecycle = state.lifecycle
|
let lifecycle = state.lifecycle
|
||||||
|
|
||||||
// OpenRouter-style `reasoning_details` ships an array of typed reasoning
|
if (delta?.reasoning_content)
|
||||||
// objects (summary / text / encrypted). Concatenate the plaintext fields
|
lifecycle = Lifecycle.reasoningDelta(lifecycle, events, "reasoning-0", delta.reasoning_content)
|
||||||
// into the reasoning delta stream; the structured array is preserved on
|
|
||||||
// the assistant message for round-trip via `providerMetadata`.
|
|
||||||
const detailText = (delta?.reasoning_details ?? [])
|
|
||||||
.map((detail) => (typeof detail.text === "string" ? detail.text : typeof detail.summary === "string" ? detail.summary : ""))
|
|
||||||
.join("")
|
|
||||||
const reasoning = delta?.reasoning_content ?? delta?.reasoning ?? (detailText.length > 0 ? detailText : undefined)
|
|
||||||
if (reasoning) lifecycle = Lifecycle.reasoningDelta(lifecycle, events, "reasoning-0", reasoning)
|
|
||||||
|
|
||||||
if (delta?.content) lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", delta.content)
|
if (delta?.content) lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", delta.content)
|
||||||
|
|
||||||
|
|||||||
@@ -1,84 +1,22 @@
|
|||||||
import { Effect, Schema } from "effect"
|
|
||||||
import { Route, type RouteRoutedModelInput } from "../route/client"
|
import { Route, type RouteRoutedModelInput } from "../route/client"
|
||||||
import { Endpoint } from "../route/endpoint"
|
import { Endpoint } from "../route/endpoint"
|
||||||
import { Framing } from "../route/framing"
|
import { Framing } from "../route/framing"
|
||||||
import { Protocol } from "../route/protocol"
|
|
||||||
import type { LLMRequest } from "../schema"
|
|
||||||
import { ProviderOptions } from "./utils/provider-options"
|
|
||||||
import * as OpenAIChat from "./openai-chat"
|
import * as OpenAIChat from "./openai-chat"
|
||||||
|
|
||||||
const ADAPTER = "openai-compatible-chat"
|
const ADAPTER = "openai-compatible-chat"
|
||||||
|
|
||||||
export type OpenAICompatibleChatModelInput = RouteRoutedModelInput
|
export type OpenAICompatibleChatModelInput = RouteRoutedModelInput
|
||||||
|
|
||||||
const OpenAICompatibleChatBody = Schema.StructWithRest(
|
|
||||||
Schema.Struct({ ...OpenAIChat.bodyFields, reasoning_effort: Schema.optional(Schema.String) }),
|
|
||||||
[Schema.Record(Schema.String, Schema.Any)],
|
|
||||||
)
|
|
||||||
export type OpenAICompatibleChatBody = Schema.Schema.Type<typeof OpenAICompatibleChatBody>
|
|
||||||
|
|
||||||
// Typed AI SDK `@ai-sdk/openai-compatible` options. Known keys are lowered
|
|
||||||
// explicitly; everything else passes through to the wire body with its
|
|
||||||
// top-level key snake-cased.
|
|
||||||
interface CompatibleOptions {
|
|
||||||
readonly user?: string
|
|
||||||
readonly reasoningEffort?: string
|
|
||||||
readonly textVerbosity?: string
|
|
||||||
readonly strictJsonSchema?: boolean
|
|
||||||
readonly [extra: string]: unknown
|
|
||||||
}
|
|
||||||
const COMPATIBLE_KNOWN_KEYS: ReadonlySet<string> = new Set([
|
|
||||||
"user",
|
|
||||||
"reasoningEffort",
|
|
||||||
"textVerbosity",
|
|
||||||
"strictJsonSchema",
|
|
||||||
])
|
|
||||||
|
|
||||||
// Match AI SDK `@ai-sdk/openai-compatible` option resolution: the deprecated
|
|
||||||
// `openai-compatible` alias, the canonical `openaiCompatible` key, the raw
|
|
||||||
// provider name (dot-split so e.g. `opencode.internal` matches `opencode`),
|
|
||||||
// and its camelCase variant. Later sources override earlier ones.
|
|
||||||
const bodyOptions = (request: LLMRequest) => {
|
|
||||||
const provider = String(request.model.provider).split(".")[0]
|
|
||||||
const camel = provider.replace(/[_-]([a-z])/g, (_, value: string) => value.toUpperCase())
|
|
||||||
const options = ProviderOptions.merge(request, [
|
|
||||||
"openai-compatible",
|
|
||||||
"openaiCompatible",
|
|
||||||
provider,
|
|
||||||
camel,
|
|
||||||
]) as CompatibleOptions
|
|
||||||
return {
|
|
||||||
...ProviderOptions.passthrough(options, COMPATIBLE_KNOWN_KEYS),
|
|
||||||
...(options.user !== undefined ? { user: options.user } : {}),
|
|
||||||
...(options.reasoningEffort !== undefined ? { reasoning_effort: options.reasoningEffort } : {}),
|
|
||||||
...(options.textVerbosity !== undefined ? { verbosity: options.textVerbosity } : {}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const protocol = Protocol.make({
|
|
||||||
id: ADAPTER,
|
|
||||||
body: {
|
|
||||||
schema: OpenAICompatibleChatBody,
|
|
||||||
// Drop providerOptions before delegating so OpenAI Chat's OpenAI-only
|
|
||||||
// option validation does not reject compatible-route requests whose
|
|
||||||
// provider id happens to be `openai` or use extended reasoning efforts.
|
|
||||||
from: (request) =>
|
|
||||||
OpenAIChat.protocol.body
|
|
||||||
.from({ ...request, providerOptions: undefined })
|
|
||||||
.pipe(Effect.map((body) => ({ ...body, ...bodyOptions(request) }))),
|
|
||||||
},
|
|
||||||
stream: OpenAIChat.protocol.stream,
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Route for non-OpenAI providers that expose an OpenAI Chat-compatible
|
* Route for non-OpenAI providers that expose an OpenAI Chat-compatible
|
||||||
* `/chat/completions` endpoint. Reuses OpenAI Chat streaming behavior while
|
* `/chat/completions` endpoint. Reuses `OpenAIChat.protocol` end-to-end and
|
||||||
* allowing compatible providers to pass through additional request-body
|
* overrides only the route id so providers can be resolved per-family without
|
||||||
* options such as `enable_thinking` and extended reasoning efforts.
|
* colliding with native OpenAI. Provider helpers configure the route endpoint
|
||||||
|
* before model selection.
|
||||||
*/
|
*/
|
||||||
export const route = Route.make({
|
export const route = Route.make({
|
||||||
id: ADAPTER,
|
id: ADAPTER,
|
||||||
protocol,
|
protocol: OpenAIChat.protocol,
|
||||||
endpoint: Endpoint.path("/chat/completions"),
|
endpoint: Endpoint.path("/chat/completions"),
|
||||||
framing: Framing.sse,
|
framing: Framing.sse,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
} from "../schema"
|
} from "../schema"
|
||||||
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared"
|
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared"
|
||||||
import { OpenAIOptions } from "./utils/openai-options"
|
import { OpenAIOptions } from "./utils/openai-options"
|
||||||
import { ProviderOptions } from "./utils/provider-options"
|
|
||||||
import { Lifecycle } from "./utils/lifecycle"
|
import { Lifecycle } from "./utils/lifecycle"
|
||||||
import { ToolStream } from "./utils/tool-stream"
|
import { ToolStream } from "./utils/tool-stream"
|
||||||
|
|
||||||
@@ -58,6 +57,11 @@ const OpenAIResponsesReasoningItem = Schema.Struct({
|
|||||||
encrypted_content: optionalNull(Schema.String),
|
encrypted_content: optionalNull(Schema.String),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const OpenAIResponsesItemReference = Schema.Struct({
|
||||||
|
type: Schema.tag("item_reference"),
|
||||||
|
id: Schema.String,
|
||||||
|
})
|
||||||
|
|
||||||
// `function_call_output.output` accepts either a plain string or an ordered
|
// `function_call_output.output` accepts either a plain string or an ordered
|
||||||
// array of content items so tools can return images in addition to text.
|
// array of content items so tools can return images in addition to text.
|
||||||
// https://platform.openai.com/docs/api-reference/responses/object
|
// https://platform.openai.com/docs/api-reference/responses/object
|
||||||
@@ -73,6 +77,7 @@ const OpenAIResponsesInputItem = Schema.Union([
|
|||||||
Schema.Struct({ role: Schema.tag("user"), content: Schema.Array(OpenAIResponsesInputContent) }),
|
Schema.Struct({ role: Schema.tag("user"), content: Schema.Array(OpenAIResponsesInputContent) }),
|
||||||
Schema.Struct({ role: Schema.tag("assistant"), content: Schema.Array(OpenAIResponsesOutputText) }),
|
Schema.Struct({ role: Schema.tag("assistant"), content: Schema.Array(OpenAIResponsesOutputText) }),
|
||||||
OpenAIResponsesReasoningItem,
|
OpenAIResponsesReasoningItem,
|
||||||
|
OpenAIResponsesItemReference,
|
||||||
Schema.Struct({
|
Schema.Struct({
|
||||||
type: Schema.tag("function_call"),
|
type: Schema.tag("function_call"),
|
||||||
call_id: Schema.String,
|
call_id: Schema.String,
|
||||||
@@ -87,6 +92,15 @@ const OpenAIResponsesInputItem = Schema.Union([
|
|||||||
])
|
])
|
||||||
type OpenAIResponsesInputItem = Schema.Schema.Type<typeof OpenAIResponsesInputItem>
|
type OpenAIResponsesInputItem = Schema.Schema.Type<typeof OpenAIResponsesInputItem>
|
||||||
|
|
||||||
|
// Mutable counterpart of the schema reasoning item so `lowerMessages` can fold
|
||||||
|
// multiple streamed summary parts into the same item before flushing.
|
||||||
|
type OpenAIResponsesReasoningInput = {
|
||||||
|
type: "reasoning"
|
||||||
|
id: string
|
||||||
|
summary: Array<{ type: "summary_text"; text: string }>
|
||||||
|
encrypted_content?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
const OpenAIResponsesTool = Schema.Struct({
|
const OpenAIResponsesTool = Schema.Struct({
|
||||||
type: Schema.tag("function"),
|
type: Schema.tag("function"),
|
||||||
name: Schema.String,
|
name: Schema.String,
|
||||||
@@ -112,23 +126,12 @@ const OpenAIResponsesCoreFields = {
|
|||||||
tools: optionalArray(OpenAIResponsesTool),
|
tools: optionalArray(OpenAIResponsesTool),
|
||||||
tool_choice: Schema.optional(OpenAIResponsesToolChoice),
|
tool_choice: Schema.optional(OpenAIResponsesToolChoice),
|
||||||
store: Schema.optional(Schema.Boolean),
|
store: Schema.optional(Schema.Boolean),
|
||||||
conversation: Schema.optional(Schema.String),
|
|
||||||
max_tool_calls: Schema.optional(Schema.Number),
|
|
||||||
metadata: Schema.optional(JsonObject),
|
|
||||||
parallel_tool_calls: Schema.optional(Schema.Boolean),
|
|
||||||
previous_response_id: Schema.optional(Schema.String),
|
|
||||||
prompt_cache_key: Schema.optional(Schema.String),
|
prompt_cache_key: Schema.optional(Schema.String),
|
||||||
prompt_cache_retention: Schema.optional(Schema.String),
|
include: optionalArray(OpenAIOptions.OpenAIResponseIncludable),
|
||||||
safety_identifier: Schema.optional(Schema.String),
|
|
||||||
service_tier: Schema.optional(Schema.String),
|
|
||||||
top_logprobs: Schema.optional(Schema.Number),
|
|
||||||
truncation: Schema.optional(Schema.String),
|
|
||||||
user: Schema.optional(Schema.String),
|
|
||||||
include: optionalArray(Schema.String),
|
|
||||||
reasoning: Schema.optional(
|
reasoning: Schema.optional(
|
||||||
Schema.Struct({
|
Schema.Struct({
|
||||||
effort: Schema.optional(OpenAIOptions.OpenAIReasoningEffort),
|
effort: Schema.optional(OpenAIOptions.OpenAIReasoningEffort),
|
||||||
summary: Schema.optional(Schema.String),
|
summary: Schema.optional(Schema.Literal("auto")),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
text: Schema.optional(
|
text: Schema.optional(
|
||||||
@@ -141,15 +144,10 @@ const OpenAIResponsesCoreFields = {
|
|||||||
top_p: Schema.optional(Schema.Number),
|
top_p: Schema.optional(Schema.Number),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unknown provider options are passed through verbatim with their top-level
|
const OpenAIResponsesBody = Schema.Struct({
|
||||||
// key snake-cased; the rest record validates them against any JSON value.
|
|
||||||
const OpenAIResponsesBody = Schema.StructWithRest(
|
|
||||||
Schema.Struct({
|
|
||||||
...OpenAIResponsesCoreFields,
|
...OpenAIResponsesCoreFields,
|
||||||
stream: Schema.Literal(true),
|
stream: Schema.Literal(true),
|
||||||
}),
|
})
|
||||||
[Schema.Record(Schema.String, Schema.Any)],
|
|
||||||
)
|
|
||||||
export type OpenAIResponsesBody = Schema.Schema.Type<typeof OpenAIResponsesBody>
|
export type OpenAIResponsesBody = Schema.Schema.Type<typeof OpenAIResponsesBody>
|
||||||
|
|
||||||
const OpenAIResponsesWebSocketMessage = Schema.StructWithRest(
|
const OpenAIResponsesWebSocketMessage = Schema.StructWithRest(
|
||||||
@@ -210,6 +208,7 @@ const OpenAIResponsesEvent = Schema.Struct({
|
|||||||
type: Schema.String,
|
type: Schema.String,
|
||||||
delta: Schema.optional(Schema.String),
|
delta: Schema.optional(Schema.String),
|
||||||
item_id: Schema.optional(Schema.String),
|
item_id: Schema.optional(Schema.String),
|
||||||
|
summary_index: Schema.optional(Schema.Number),
|
||||||
item: Schema.optional(OpenAIResponsesStreamItem),
|
item: Schema.optional(OpenAIResponsesStreamItem),
|
||||||
response: Schema.optional(
|
response: Schema.optional(
|
||||||
Schema.StructWithRest(
|
Schema.StructWithRest(
|
||||||
@@ -233,6 +232,18 @@ interface ParserState {
|
|||||||
readonly tools: ToolStream.State<string>
|
readonly tools: ToolStream.State<string>
|
||||||
readonly hasFunctionCall: boolean
|
readonly hasFunctionCall: boolean
|
||||||
readonly lifecycle: Lifecycle.State
|
readonly lifecycle: Lifecycle.State
|
||||||
|
readonly reasoningItems: Readonly<Record<string, ReasoningStreamItem>>
|
||||||
|
readonly store: boolean | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReasoningSummaryStatus = "active" | "can-conclude" | "concluded"
|
||||||
|
|
||||||
|
interface ReasoningStreamItem {
|
||||||
|
readonly encryptedContent: string | null | undefined
|
||||||
|
// Keyed by OpenAI's numeric `summary_index`. JS object keys coerce to
|
||||||
|
// strings, but typing the map as `Record<number, ...>` documents intent
|
||||||
|
// and matches the wire field.
|
||||||
|
readonly summaryParts: Readonly<Record<number, ReasoningSummaryStatus>>
|
||||||
}
|
}
|
||||||
|
|
||||||
const invalid = ProviderShared.invalidRequest
|
const invalid = ProviderShared.invalidRequest
|
||||||
@@ -262,22 +273,21 @@ const lowerToolCall = (part: ToolCallPart): OpenAIResponsesInputItem => ({
|
|||||||
arguments: ProviderShared.encodeJson(part.input),
|
arguments: ProviderShared.encodeJson(part.input),
|
||||||
})
|
})
|
||||||
|
|
||||||
const lowerReasoning = (part: ReasoningPart, store: boolean | undefined): OpenAIResponsesInputItem | undefined => {
|
const lowerReasoning = (part: ReasoningPart): OpenAIResponsesReasoningInput | undefined => {
|
||||||
const openai = part.providerMetadata?.openai
|
const openai = part.providerMetadata?.openai
|
||||||
if (!ProviderShared.isRecord(openai) || typeof openai.itemId !== "string") return undefined
|
if (!ProviderShared.isRecord(openai) || typeof openai.itemId !== "string" || openai.itemId.length === 0)
|
||||||
// With store:false, OpenAI only accepts previous reasoning items when the
|
return undefined
|
||||||
// encrypted state is present. Bare rs_* ids point to non-persisted items.
|
const encryptedContent =
|
||||||
if (store === false && typeof openai.reasoningEncryptedContent !== "string") return undefined
|
|
||||||
return {
|
|
||||||
type: "reasoning",
|
|
||||||
id: openai.itemId,
|
|
||||||
summary: part.text.length > 0 ? [{ type: "summary_text", text: part.text }] : [],
|
|
||||||
encrypted_content:
|
|
||||||
typeof openai.reasoningEncryptedContent === "string"
|
typeof openai.reasoningEncryptedContent === "string"
|
||||||
? openai.reasoningEncryptedContent
|
? openai.reasoningEncryptedContent
|
||||||
: openai.reasoningEncryptedContent === null
|
: openai.reasoningEncryptedContent === null
|
||||||
? null
|
? null
|
||||||
: undefined,
|
: undefined
|
||||||
|
return {
|
||||||
|
type: "reasoning",
|
||||||
|
id: openai.itemId,
|
||||||
|
summary: part.text.length > 0 ? [{ type: "summary_text", text: part.text }] : [],
|
||||||
|
encrypted_content: encryptedContent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,15 +320,14 @@ const lowerToolResultOutput = Effect.fn("OpenAIResponses.lowerToolResultOutput")
|
|||||||
// Text/json/error results are encoded as a plain string for backward
|
// Text/json/error results are encoded as a plain string for backward
|
||||||
// compatibility with existing cassettes and provider expectations.
|
// compatibility with existing cassettes and provider expectations.
|
||||||
if (part.result.type !== "content") return ProviderShared.toolResultText(part)
|
if (part.result.type !== "content") return ProviderShared.toolResultText(part)
|
||||||
const items: ReadonlyArray<ToolResultContentPart> = part.result.value
|
return yield* Effect.forEach(part.result.value, lowerToolResultContentItem)
|
||||||
return yield* Effect.forEach(items, lowerToolResultContentItem)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const lowerMessages = Effect.fn("OpenAIResponses.lowerMessages")(function* (request: LLMRequest) {
|
const lowerMessages = Effect.fn("OpenAIResponses.lowerMessages")(function* (request: LLMRequest) {
|
||||||
const system: OpenAIResponsesInputItem[] =
|
const system: OpenAIResponsesInputItem[] =
|
||||||
request.system.length === 0 ? [] : [{ role: "system", content: ProviderShared.joinText(request.system) }]
|
request.system.length === 0 ? [] : [{ role: "system", content: ProviderShared.joinText(request.system) }]
|
||||||
const input: OpenAIResponsesInputItem[] = [...system]
|
const input: OpenAIResponsesInputItem[] = [...system]
|
||||||
const store = OpenAIOptions.options(request).store
|
const store = OpenAIOptions.store(request)
|
||||||
|
|
||||||
for (const message of request.messages) {
|
for (const message of request.messages) {
|
||||||
if (message.role === "user") {
|
if (message.role === "user") {
|
||||||
@@ -328,6 +337,8 @@ const lowerMessages = Effect.fn("OpenAIResponses.lowerMessages")(function* (requ
|
|||||||
|
|
||||||
if (message.role === "assistant") {
|
if (message.role === "assistant") {
|
||||||
const content: TextPart[] = []
|
const content: TextPart[] = []
|
||||||
|
const reasoningItems: Record<string, OpenAIResponsesReasoningInput> = {}
|
||||||
|
const reasoningReferences = new Set<string>()
|
||||||
const flushText = () => {
|
const flushText = () => {
|
||||||
if (content.length === 0) return
|
if (content.length === 0) return
|
||||||
input.push({ role: "assistant", content: content.map((part) => ({ type: "output_text", text: part.text })) })
|
input.push({ role: "assistant", content: content.map((part) => ({ type: "output_text", text: part.text })) })
|
||||||
@@ -340,8 +351,22 @@ const lowerMessages = Effect.fn("OpenAIResponses.lowerMessages")(function* (requ
|
|||||||
}
|
}
|
||||||
if (part.type === "reasoning") {
|
if (part.type === "reasoning") {
|
||||||
flushText()
|
flushText()
|
||||||
const reasoning = lowerReasoning(part, store)
|
const reasoning = lowerReasoning(part)
|
||||||
if (reasoning) input.push(reasoning)
|
if (!reasoning) continue
|
||||||
|
if (store !== false && reasoning.id) {
|
||||||
|
if (!reasoningReferences.has(reasoning.id)) input.push({ type: "item_reference", id: reasoning.id })
|
||||||
|
reasoningReferences.add(reasoning.id)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const existing = reasoningItems[reasoning.id]
|
||||||
|
if (existing) {
|
||||||
|
existing.summary.push(...reasoning.summary)
|
||||||
|
if (typeof reasoning.encrypted_content === "string")
|
||||||
|
existing.encrypted_content = reasoning.encrypted_content
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
reasoningItems[reasoning.id] = reasoning
|
||||||
|
input.push(reasoning)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (part.type === "tool-call") {
|
if (part.type === "tool-call") {
|
||||||
@@ -370,50 +395,35 @@ const lowerMessages = Effect.fn("OpenAIResponses.lowerMessages")(function* (requ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return input
|
// With store:false, OpenAI only accepts previous reasoning items when the
|
||||||
|
// complete item has encrypted state. Summary blocks for one item may carry
|
||||||
|
// that state only on the last block, so filter after they have been joined.
|
||||||
|
return store === false
|
||||||
|
? input.filter(
|
||||||
|
(item) => !("type" in item) || item.type !== "reasoning" || typeof item.encrypted_content === "string",
|
||||||
|
)
|
||||||
|
: input
|
||||||
})
|
})
|
||||||
|
|
||||||
const lowerOptions = (request: LLMRequest) => {
|
const lowerOptions = Effect.fn("OpenAIResponses.lowerOptions")(function* (request: LLMRequest) {
|
||||||
const options = OpenAIOptions.options(request)
|
const store = OpenAIOptions.store(request)
|
||||||
// OpenAI Responses does not accept the `max` reasoning effort variant.
|
const promptCacheKey = OpenAIOptions.promptCacheKey(request)
|
||||||
const effort = OpenAIOptions.isReasoningEffort(options.reasoningEffort) ? options.reasoningEffort : undefined
|
const effort = OpenAIOptions.reasoningEffort(request)
|
||||||
const summary = options.reasoningSummary
|
if (effort && !OpenAIOptions.isReasoningEffort(effort))
|
||||||
const verbosity = options.textVerbosity
|
return yield* invalid(`OpenAI Responses does not support reasoning effort ${effort}`)
|
||||||
// `logprobs` is enabled only by `true` or a numeric top-N. `false` and
|
const summary = OpenAIOptions.reasoningSummary(request)
|
||||||
// `undefined` leave the request without the logprobs include + top_logprobs.
|
const include = OpenAIOptions.include(request)
|
||||||
const logprobsEnabled = options.logprobs === true || typeof options.logprobs === "number"
|
const verbosity = OpenAIOptions.textVerbosity(request)
|
||||||
const include = (() => {
|
const instructions = OpenAIOptions.instructions(request)
|
||||||
const base = options.include ? [...options.include] : []
|
|
||||||
if (options.includeEncryptedReasoning && !base.includes("reasoning.encrypted_content")) {
|
|
||||||
base.push("reasoning.encrypted_content")
|
|
||||||
}
|
|
||||||
if (logprobsEnabled && !base.includes("message.output_text.logprobs")) {
|
|
||||||
base.push("message.output_text.logprobs")
|
|
||||||
}
|
|
||||||
return base.length > 0 ? base : undefined
|
|
||||||
})()
|
|
||||||
const topLogprobs = typeof options.logprobs === "number" ? options.logprobs : options.logprobs === true ? 20 : undefined
|
|
||||||
return {
|
return {
|
||||||
...ProviderOptions.passthrough(options, OpenAIOptions.KNOWN_KEYS),
|
...(instructions ? { instructions } : {}),
|
||||||
...(options.instructions !== undefined ? { instructions: options.instructions } : {}),
|
...(store !== undefined ? { store } : {}),
|
||||||
...(options.store !== undefined ? { store: options.store } : {}),
|
...(promptCacheKey ? { prompt_cache_key: promptCacheKey } : {}),
|
||||||
...(options.conversation !== undefined ? { conversation: options.conversation } : {}),
|
|
||||||
...(options.maxToolCalls !== undefined ? { max_tool_calls: options.maxToolCalls } : {}),
|
|
||||||
...(options.metadata !== undefined ? { metadata: options.metadata } : {}),
|
|
||||||
...(options.parallelToolCalls !== undefined ? { parallel_tool_calls: options.parallelToolCalls } : {}),
|
|
||||||
...(options.previousResponseId !== undefined ? { previous_response_id: options.previousResponseId } : {}),
|
|
||||||
...(options.promptCacheKey !== undefined ? { prompt_cache_key: options.promptCacheKey } : {}),
|
|
||||||
...(options.promptCacheRetention !== undefined ? { prompt_cache_retention: options.promptCacheRetention } : {}),
|
|
||||||
...(options.safetyIdentifier !== undefined ? { safety_identifier: options.safetyIdentifier } : {}),
|
|
||||||
...(options.serviceTier !== undefined ? { service_tier: options.serviceTier } : {}),
|
|
||||||
...(topLogprobs !== undefined ? { top_logprobs: topLogprobs } : {}),
|
|
||||||
...(options.truncation !== undefined ? { truncation: options.truncation } : {}),
|
|
||||||
...(options.user !== undefined ? { user: options.user } : {}),
|
|
||||||
...(include ? { include } : {}),
|
...(include ? { include } : {}),
|
||||||
...(effort !== undefined || summary !== undefined ? { reasoning: { effort, summary } } : {}),
|
...(effort || summary ? { reasoning: { effort, summary } } : {}),
|
||||||
...(verbosity !== undefined ? { text: { verbosity } } : {}),
|
...(verbosity ? { text: { verbosity } } : {}),
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const fromRequest = Effect.fn("OpenAIResponses.fromRequest")(function* (request: LLMRequest) {
|
const fromRequest = Effect.fn("OpenAIResponses.fromRequest")(function* (request: LLMRequest) {
|
||||||
const generation = request.generation
|
const generation = request.generation
|
||||||
@@ -426,7 +436,7 @@ const fromRequest = Effect.fn("OpenAIResponses.fromRequest")(function* (request:
|
|||||||
max_output_tokens: generation?.maxTokens,
|
max_output_tokens: generation?.maxTokens,
|
||||||
temperature: generation?.temperature,
|
temperature: generation?.temperature,
|
||||||
top_p: generation?.topP,
|
top_p: generation?.topP,
|
||||||
...lowerOptions(request),
|
...(yield* lowerOptions(request)),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -557,24 +567,51 @@ const onOutputTextDelta = (state: ParserState, event: OpenAIResponsesEvent): Ste
|
|||||||
const onReasoningDelta = (state: ParserState, event: OpenAIResponsesEvent): StepResult => {
|
const onReasoningDelta = (state: ParserState, event: OpenAIResponsesEvent): StepResult => {
|
||||||
if (!event.delta) return [state, NO_EVENTS]
|
if (!event.delta) return [state, NO_EVENTS]
|
||||||
const events: LLMEvent[] = []
|
const events: LLMEvent[] = []
|
||||||
|
const itemID = event.item_id ?? "reasoning-0"
|
||||||
|
const id =
|
||||||
|
event.summary_index !== undefined || state.reasoningItems[itemID] ? `${itemID}:${event.summary_index ?? 0}` : itemID
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
...state,
|
...state,
|
||||||
lifecycle: Lifecycle.reasoningDelta(state.lifecycle, events, event.item_id ?? "reasoning-0", event.delta),
|
lifecycle: Lifecycle.reasoningDelta(state.lifecycle, events, id, event.delta),
|
||||||
},
|
},
|
||||||
events,
|
events,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// The summary done event does not carry encrypted continuation state. Finish the
|
|
||||||
// common reasoning block when the full reasoning item arrives in output_item.done.
|
|
||||||
const onReasoningDone = (state: ParserState, _event: OpenAIResponsesEvent): StepResult => [state, NO_EVENTS]
|
const onReasoningDone = (state: ParserState, _event: OpenAIResponsesEvent): StepResult => [state, NO_EVENTS]
|
||||||
|
|
||||||
const reasoningMetadata = (item: OpenAIResponsesStreamItem & { id: string }) =>
|
const reasoningMetadata = (item: OpenAIResponsesStreamItem & { id: string }) =>
|
||||||
openaiMetadata({ itemId: item.id, reasoningEncryptedContent: item.encrypted_content ?? null })
|
openaiMetadata({ itemId: item.id, reasoningEncryptedContent: item.encrypted_content ?? null })
|
||||||
|
|
||||||
|
// OpenAI Responses streams reasoning items in a stable order:
|
||||||
|
// `output_item.added` (reasoning) →
|
||||||
|
// `reasoning_summary_part.added` (index=0) →
|
||||||
|
// `reasoning_summary_text.delta` →
|
||||||
|
// `reasoning_summary_part.done` (index=0) →
|
||||||
|
// (repeat for index>0) →
|
||||||
|
// `output_item.done` (reasoning).
|
||||||
|
// The handlers below rely on this ordering: `onOutputItemAdded` seeds the
|
||||||
|
// per-item entry, `onReasoningSummaryPartAdded` for `summary_index === 0`
|
||||||
|
// short-circuits when the entry already exists, and higher-index handlers
|
||||||
|
// fold against the same entry. Behaviour for out-of-order events is
|
||||||
|
// best-effort, not guaranteed.
|
||||||
const onOutputItemAdded = (state: ParserState, event: OpenAIResponsesEvent): StepResult => {
|
const onOutputItemAdded = (state: ParserState, event: OpenAIResponsesEvent): StepResult => {
|
||||||
const item = event.item
|
const item = event.item
|
||||||
|
if (item && isReasoningItem(item)) {
|
||||||
|
const events: LLMEvent[] = []
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
...state,
|
||||||
|
lifecycle: Lifecycle.reasoningStart(state.lifecycle, events, `${item.id}:0`, reasoningMetadata(item)),
|
||||||
|
reasoningItems: {
|
||||||
|
...state.reasoningItems,
|
||||||
|
[item.id]: { encryptedContent: item.encrypted_content, summaryParts: { 0: "active" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
events,
|
||||||
|
]
|
||||||
|
}
|
||||||
if (item?.type !== "function_call" || !item.id) return [state, NO_EVENTS]
|
if (item?.type !== "function_call" || !item.id) return [state, NO_EVENTS]
|
||||||
const providerMetadata = openaiMetadata({ itemId: item.id })
|
const providerMetadata = openaiMetadata({ itemId: item.id })
|
||||||
const events: LLMEvent[] = []
|
const events: LLMEvent[] = []
|
||||||
@@ -595,6 +632,103 @@ const onOutputItemAdded = (state: ParserState, event: OpenAIResponsesEvent): Ste
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onReasoningSummaryPartAdded = (state: ParserState, event: OpenAIResponsesEvent): StepResult => {
|
||||||
|
if (!event.item_id || event.summary_index === undefined) return [state, NO_EVENTS]
|
||||||
|
const item = state.reasoningItems[event.item_id] ?? { encryptedContent: undefined, summaryParts: {} }
|
||||||
|
if (event.summary_index === 0) {
|
||||||
|
if (state.reasoningItems[event.item_id]) return [state, NO_EVENTS]
|
||||||
|
const events: LLMEvent[] = []
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
...state,
|
||||||
|
lifecycle: Lifecycle.reasoningStart(
|
||||||
|
state.lifecycle,
|
||||||
|
events,
|
||||||
|
`${event.item_id}:0`,
|
||||||
|
openaiMetadata({ itemId: event.item_id, reasoningEncryptedContent: null }),
|
||||||
|
),
|
||||||
|
reasoningItems: {
|
||||||
|
...state.reasoningItems,
|
||||||
|
[event.item_id]: { ...item, summaryParts: { 0: "active" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
events,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const events: LLMEvent[] = []
|
||||||
|
const closed = Object.entries(item.summaryParts)
|
||||||
|
.filter((entry) => entry[1] === "can-conclude")
|
||||||
|
.reduce(
|
||||||
|
(lifecycle, entry) =>
|
||||||
|
Lifecycle.reasoningEnd(
|
||||||
|
lifecycle,
|
||||||
|
events,
|
||||||
|
`${event.item_id}:${entry[0]}`,
|
||||||
|
openaiMetadata({ itemId: event.item_id }),
|
||||||
|
),
|
||||||
|
state.lifecycle,
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
...state,
|
||||||
|
lifecycle: Lifecycle.reasoningStart(
|
||||||
|
closed,
|
||||||
|
events,
|
||||||
|
`${event.item_id}:${event.summary_index}`,
|
||||||
|
openaiMetadata({ itemId: event.item_id, reasoningEncryptedContent: item.encryptedContent ?? null }),
|
||||||
|
),
|
||||||
|
reasoningItems: {
|
||||||
|
...state.reasoningItems,
|
||||||
|
[event.item_id]: {
|
||||||
|
...item,
|
||||||
|
summaryParts: {
|
||||||
|
...Object.fromEntries(
|
||||||
|
Object.entries(item.summaryParts).map((entry) =>
|
||||||
|
entry[1] === "can-conclude" ? [entry[0], "concluded" as const] : entry,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
[event.summary_index]: "active",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
events,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const onReasoningSummaryPartDone = (state: ParserState, event: OpenAIResponsesEvent): StepResult => {
|
||||||
|
if (!event.item_id || event.summary_index === undefined) return [state, NO_EVENTS]
|
||||||
|
const item = state.reasoningItems[event.item_id]
|
||||||
|
if (!item) return [state, NO_EVENTS]
|
||||||
|
const events: LLMEvent[] = []
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
...state,
|
||||||
|
lifecycle:
|
||||||
|
state.store !== false
|
||||||
|
? Lifecycle.reasoningEnd(
|
||||||
|
state.lifecycle,
|
||||||
|
events,
|
||||||
|
`${event.item_id}:${event.summary_index}`,
|
||||||
|
openaiMetadata({ itemId: event.item_id }),
|
||||||
|
)
|
||||||
|
: state.lifecycle,
|
||||||
|
reasoningItems: {
|
||||||
|
...state.reasoningItems,
|
||||||
|
[event.item_id]: {
|
||||||
|
...item,
|
||||||
|
summaryParts: {
|
||||||
|
...item.summaryParts,
|
||||||
|
[event.summary_index]: state.store !== false ? "concluded" : "can-conclude",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
events,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
const onFunctionCallArgumentsDelta = Effect.fn("OpenAIResponses.onFunctionCallArgumentsDelta")(function* (
|
const onFunctionCallArgumentsDelta = Effect.fn("OpenAIResponses.onFunctionCallArgumentsDelta")(function* (
|
||||||
state: ParserState,
|
state: ParserState,
|
||||||
event: OpenAIResponsesEvent,
|
event: OpenAIResponsesEvent,
|
||||||
@@ -655,6 +789,17 @@ const onOutputItemDone = Effect.fn("OpenAIResponses.onOutputItemDone")(function*
|
|||||||
if (isReasoningItem(item)) {
|
if (isReasoningItem(item)) {
|
||||||
const events: LLMEvent[] = []
|
const events: LLMEvent[] = []
|
||||||
const providerMetadata = reasoningMetadata(item)
|
const providerMetadata = reasoningMetadata(item)
|
||||||
|
const reasoningItem = state.reasoningItems[item.id]
|
||||||
|
if (reasoningItem) {
|
||||||
|
const lifecycle = Object.entries(reasoningItem.summaryParts)
|
||||||
|
.filter((entry) => entry[1] === "active" || entry[1] === "can-conclude")
|
||||||
|
.reduce(
|
||||||
|
(lifecycle, entry) => Lifecycle.reasoningEnd(lifecycle, events, `${item.id}:${entry[0]}`, providerMetadata),
|
||||||
|
state.lifecycle,
|
||||||
|
)
|
||||||
|
const { [item.id]: _removed, ...reasoningItems } = state.reasoningItems
|
||||||
|
return [{ ...state, lifecycle, reasoningItems }, events] satisfies StepResult
|
||||||
|
}
|
||||||
if (!state.lifecycle.reasoning.has(item.id)) {
|
if (!state.lifecycle.reasoning.has(item.id)) {
|
||||||
const lifecycle = Lifecycle.stepStart(state.lifecycle, events)
|
const lifecycle = Lifecycle.stepStart(state.lifecycle, events)
|
||||||
events.push(LLMEvent.reasoningStart({ id: item.id, providerMetadata }))
|
events.push(LLMEvent.reasoningStart({ id: item.id, providerMetadata }))
|
||||||
@@ -723,6 +868,10 @@ const step = (state: ParserState, event: OpenAIResponsesEvent) => {
|
|||||||
event.type === "response.reasoning_summary_text.done"
|
event.type === "response.reasoning_summary_text.done"
|
||||||
)
|
)
|
||||||
return Effect.succeed(onReasoningDone(state, event))
|
return Effect.succeed(onReasoningDone(state, event))
|
||||||
|
if (event.type === "response.reasoning_summary_part.added")
|
||||||
|
return Effect.succeed(onReasoningSummaryPartAdded(state, event))
|
||||||
|
if (event.type === "response.reasoning_summary_part.done")
|
||||||
|
return Effect.succeed(onReasoningSummaryPartDone(state, event))
|
||||||
if (event.type === "response.output_item.added") return Effect.succeed(onOutputItemAdded(state, event))
|
if (event.type === "response.output_item.added") return Effect.succeed(onOutputItemAdded(state, event))
|
||||||
if (event.type === "response.function_call_arguments.delta") return onFunctionCallArgumentsDelta(state, event)
|
if (event.type === "response.function_call_arguments.delta") return onFunctionCallArgumentsDelta(state, event)
|
||||||
if (event.type === "response.output_item.done") return onOutputItemDone(state, event)
|
if (event.type === "response.output_item.done") return onOutputItemDone(state, event)
|
||||||
@@ -749,7 +898,13 @@ export const protocol = Protocol.make({
|
|||||||
},
|
},
|
||||||
stream: {
|
stream: {
|
||||||
event: Protocol.jsonEvent(OpenAIResponsesEvent),
|
event: Protocol.jsonEvent(OpenAIResponsesEvent),
|
||||||
initial: () => ({ hasFunctionCall: false, tools: ToolStream.empty<string>(), lifecycle: Lifecycle.initial() }),
|
initial: (request) => ({
|
||||||
|
hasFunctionCall: false,
|
||||||
|
tools: ToolStream.empty<string>(),
|
||||||
|
lifecycle: Lifecycle.initial(),
|
||||||
|
reasoningItems: {},
|
||||||
|
store: OpenAIOptions.store(request),
|
||||||
|
}),
|
||||||
step,
|
step,
|
||||||
terminal: (event) => TERMINAL_TYPES.has(event.type),
|
terminal: (event) => TERMINAL_TYPES.has(event.type),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -24,16 +24,24 @@ export const textDelta = (state: State, events: LLMEvent[], id: string, text: st
|
|||||||
return { ...stepped, text: new Set([...stepped.text, id]) }
|
return { ...stepped, text: new Set([...stepped.text, id]) }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const reasoningDelta = (state: State, events: LLMEvent[], id: string, text: string): State => {
|
export const reasoningStart = (
|
||||||
|
state: State,
|
||||||
|
events: LLMEvent[],
|
||||||
|
id: string,
|
||||||
|
providerMetadata?: ProviderMetadata,
|
||||||
|
): State => {
|
||||||
|
if (state.reasoning.has(id)) return state
|
||||||
const stepped = stepStart(state, events)
|
const stepped = stepStart(state, events)
|
||||||
if (stepped.reasoning.has(id)) {
|
events.push(LLMEvent.reasoningStart({ id, providerMetadata }))
|
||||||
events.push(LLMEvent.reasoningDelta({ id, text }))
|
|
||||||
return stepped
|
|
||||||
}
|
|
||||||
events.push(LLMEvent.reasoningStart({ id }), LLMEvent.reasoningDelta({ id, text }))
|
|
||||||
return { ...stepped, reasoning: new Set([...stepped.reasoning, id]) }
|
return { ...stepped, reasoning: new Set([...stepped.reasoning, id]) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const reasoningDelta = (state: State, events: LLMEvent[], id: string, text: string): State => {
|
||||||
|
const started = reasoningStart(state, events, id)
|
||||||
|
events.push(LLMEvent.reasoningDelta({ id, text }))
|
||||||
|
return started
|
||||||
|
}
|
||||||
|
|
||||||
export const reasoningEnd = (
|
export const reasoningEnd = (
|
||||||
state: State,
|
state: State,
|
||||||
events: LLMEvent[],
|
events: LLMEvent[],
|
||||||
|
|||||||
@@ -1,73 +1,84 @@
|
|||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
import type { LLMRequest } from "../../schema"
|
import type { LLMRequest, ReasoningEffort, TextVerbosity as TextVerbosityValue } from "../../schema"
|
||||||
import { ReasoningEfforts, TextVerbosity, type ReasoningEffort } from "../../schema"
|
import { ReasoningEfforts, TextVerbosity } from "../../schema"
|
||||||
import { ProviderOptions } from "./provider-options"
|
|
||||||
|
|
||||||
export const OpenAIReasoningEfforts = ReasoningEfforts.filter(
|
export const OpenAIReasoningEfforts = ReasoningEfforts.filter(
|
||||||
(effort): effort is Exclude<ReasoningEffort, "max"> => effort !== "max",
|
(effort): effort is Exclude<ReasoningEffort, "max"> => effort !== "max",
|
||||||
)
|
)
|
||||||
export type OpenAIReasoningEffort = (typeof OpenAIReasoningEfforts)[number]
|
export type OpenAIReasoningEffort = (typeof OpenAIReasoningEfforts)[number]
|
||||||
|
|
||||||
|
// Mirrors OpenAI's `ResponseIncludable` union from the official SDK. Keep this
|
||||||
|
// in lockstep with `openai-node/src/resources/responses/responses.ts`.
|
||||||
|
export const OpenAIResponseIncludables = [
|
||||||
|
"file_search_call.results",
|
||||||
|
"web_search_call.results",
|
||||||
|
"web_search_call.action.sources",
|
||||||
|
"message.input_image.image_url",
|
||||||
|
"computer_call_output.output.image_url",
|
||||||
|
"code_interpreter_call.outputs",
|
||||||
|
"reasoning.encrypted_content",
|
||||||
|
"message.output_text.logprobs",
|
||||||
|
] as const
|
||||||
|
export type OpenAIResponseIncludable = (typeof OpenAIResponseIncludables)[number]
|
||||||
|
|
||||||
|
const REASONING_EFFORTS = new Set<string>(ReasoningEfforts)
|
||||||
const OPENAI_REASONING_EFFORTS = new Set<string>(OpenAIReasoningEfforts)
|
const OPENAI_REASONING_EFFORTS = new Set<string>(OpenAIReasoningEfforts)
|
||||||
|
const TEXT_VERBOSITY = new Set<string>(["low", "medium", "high"])
|
||||||
|
const INCLUDABLES = new Set<string>(OpenAIResponseIncludables)
|
||||||
|
|
||||||
export const OpenAIReasoningEffort = Schema.Literals(OpenAIReasoningEfforts)
|
export const OpenAIReasoningEffort = Schema.Literals(OpenAIReasoningEfforts)
|
||||||
export const OpenAITextVerbosity = TextVerbosity
|
export const OpenAITextVerbosity = TextVerbosity
|
||||||
|
export const OpenAIResponseIncludable = Schema.Literals(OpenAIResponseIncludables)
|
||||||
|
|
||||||
|
const isAnyReasoningEffort = (effort: unknown): effort is ReasoningEffort =>
|
||||||
|
typeof effort === "string" && REASONING_EFFORTS.has(effort)
|
||||||
|
|
||||||
export const isReasoningEffort = (effort: unknown): effort is OpenAIReasoningEffort =>
|
export const isReasoningEffort = (effort: unknown): effort is OpenAIReasoningEffort =>
|
||||||
typeof effort === "string" && OPENAI_REASONING_EFFORTS.has(effort)
|
typeof effort === "string" && OPENAI_REASONING_EFFORTS.has(effort)
|
||||||
|
|
||||||
// Typed AI SDK OpenAI options. Mirrors the camelCase surface AI SDK accepts.
|
const isTextVerbosity = (value: unknown): value is TextVerbosityValue =>
|
||||||
// Known keys are typed; everything else passes through to the wire body with
|
typeof value === "string" && TEXT_VERBOSITY.has(value)
|
||||||
// its top-level key snake-cased.
|
|
||||||
export interface Options {
|
const options = (request: LLMRequest) => request.providerOptions?.openai
|
||||||
readonly store?: boolean
|
|
||||||
readonly promptCacheKey?: string
|
export const store = (request: LLMRequest): boolean | undefined => {
|
||||||
readonly promptCacheRetention?: string
|
const value = options(request)?.store
|
||||||
readonly reasoningEffort?: ReasoningEffort
|
return typeof value === "boolean" ? value : undefined
|
||||||
readonly reasoningSummary?: string
|
|
||||||
readonly textVerbosity?: "low" | "medium" | "high"
|
|
||||||
readonly include?: ReadonlyArray<string>
|
|
||||||
readonly includeEncryptedReasoning?: boolean
|
|
||||||
readonly instructions?: string
|
|
||||||
readonly conversation?: string
|
|
||||||
readonly maxToolCalls?: number
|
|
||||||
readonly metadata?: Record<string, unknown>
|
|
||||||
readonly parallelToolCalls?: boolean
|
|
||||||
readonly previousResponseId?: string
|
|
||||||
readonly safetyIdentifier?: string
|
|
||||||
readonly serviceTier?: string
|
|
||||||
readonly logprobs?: boolean | number
|
|
||||||
readonly truncation?: string
|
|
||||||
readonly user?: string
|
|
||||||
readonly [extra: string]: unknown
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const KNOWN_KEYS: ReadonlySet<string> = new Set([
|
export const reasoningEffort = (request: LLMRequest): ReasoningEffort | undefined => {
|
||||||
"store",
|
const value = options(request)?.reasoningEffort
|
||||||
"promptCacheKey",
|
return isAnyReasoningEffort(value) ? value : undefined
|
||||||
"promptCacheRetention",
|
}
|
||||||
"reasoningEffort",
|
|
||||||
"reasoningSummary",
|
|
||||||
"textVerbosity",
|
|
||||||
"include",
|
|
||||||
"includeEncryptedReasoning",
|
|
||||||
"instructions",
|
|
||||||
"conversation",
|
|
||||||
"maxToolCalls",
|
|
||||||
"metadata",
|
|
||||||
"parallelToolCalls",
|
|
||||||
"previousResponseId",
|
|
||||||
"safetyIdentifier",
|
|
||||||
"serviceTier",
|
|
||||||
"logprobs",
|
|
||||||
"truncation",
|
|
||||||
"user",
|
|
||||||
])
|
|
||||||
|
|
||||||
// Read the merged `openai` provider option bag. Producers
|
export const reasoningSummary = (request: LLMRequest): "auto" | undefined =>
|
||||||
// (`packages/opencode/src/provider/transform.ts`) emit typed values; we widen
|
options(request)?.reasoningSummary === "auto" ? "auto" : undefined
|
||||||
// only the index signature so passthrough keys remain reachable. Invalid
|
|
||||||
// shapes surface in the lowerer where they're consumed, not at decode time.
|
// Resolve the OpenAI Responses `include` field. Filters out unknown
|
||||||
export const options = (request: LLMRequest): Options => ProviderOptions.merge(request, ["openai"]) as Options
|
// includable values defensively so a typo in upstream config drops the
|
||||||
|
// invalid entry instead of poisoning the wire body. An empty array (either
|
||||||
|
// passed directly or produced by filtering) is treated as "no include" and
|
||||||
|
// returns undefined so the request body omits the field entirely.
|
||||||
|
export const include = (request: LLMRequest): ReadonlyArray<OpenAIResponseIncludable> | undefined => {
|
||||||
|
const value = options(request)?.include
|
||||||
|
if (!Array.isArray(value)) return undefined
|
||||||
|
const filtered = value.filter((entry): entry is OpenAIResponseIncludable => INCLUDABLES.has(entry))
|
||||||
|
return filtered.length > 0 ? filtered : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export const promptCacheKey = (request: LLMRequest) => {
|
||||||
|
const value = options(request)?.promptCacheKey
|
||||||
|
return typeof value === "string" ? value : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export const textVerbosity = (request: LLMRequest) => {
|
||||||
|
const value = options(request)?.textVerbosity
|
||||||
|
return isTextVerbosity(value) ? value : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export const instructions = (request: LLMRequest) => {
|
||||||
|
const value = options(request)?.instructions
|
||||||
|
return typeof value === "string" ? value : undefined
|
||||||
|
}
|
||||||
|
|
||||||
export * as OpenAIOptions from "./openai-options"
|
export * as OpenAIOptions from "./openai-options"
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
import type { LLMRequest } from "../../schema"
|
|
||||||
|
|
||||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
||||||
typeof value === "object" && value !== null && !Array.isArray(value)
|
|
||||||
|
|
||||||
// Convert a single top-level option key from camelCase to snake_case. Values
|
|
||||||
// are left verbatim — recursive conversion would mangle structured payloads
|
|
||||||
// (IDs, nested provider-shaped objects) and provider APIs do not require it.
|
|
||||||
// PascalCase (`FooBar`) becomes `foo_bar` without a leading underscore.
|
|
||||||
export const snakeKey = (key: string) =>
|
|
||||||
key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase()
|
|
||||||
|
|
||||||
// Merge provider option namespaces using AI SDK precedence semantics: later
|
|
||||||
// sources override earlier ones, missing namespaces are skipped. Used by every
|
|
||||||
// native protocol that reads request-level provider options.
|
|
||||||
export const merge = (request: LLMRequest, keys: ReadonlyArray<string>) => {
|
|
||||||
const sources = keys.map((key) => request.providerOptions?.[key]).filter(isRecord)
|
|
||||||
return Object.assign({}, ...sources) as Record<string, unknown>
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spread the unknown remainder of a merged option bag onto a provider body.
|
|
||||||
// `consumed` lists keys already lowered explicitly so they aren't duplicated
|
|
||||||
// or echoed at the wrong shape.
|
|
||||||
export const passthrough = (options: Record<string, unknown>, consumed: ReadonlySet<string>) => {
|
|
||||||
const result: Record<string, unknown> = {}
|
|
||||||
for (const [key, value] of Object.entries(options)) {
|
|
||||||
if (consumed.has(key)) continue
|
|
||||||
if (value === undefined) continue
|
|
||||||
result[snakeKey(key)] = value
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
export * as ProviderOptions from "./provider-options"
|
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
import type { ProviderOptions, ReasoningEffort, TextVerbosity } from "../schema"
|
import type { ProviderOptions, ReasoningEffort, TextVerbosity } from "../schema"
|
||||||
import { mergeProviderOptions } from "../schema"
|
import { mergeProviderOptions } from "../schema"
|
||||||
|
import type { OpenAIResponseIncludable } from "../protocols/utils/openai-options"
|
||||||
|
|
||||||
|
export type { OpenAIResponseIncludable } from "../protocols/utils/openai-options"
|
||||||
|
|
||||||
export interface OpenAIOptionsInput {
|
export interface OpenAIOptionsInput {
|
||||||
readonly [key: string]: unknown
|
readonly [key: string]: unknown
|
||||||
@@ -7,7 +10,10 @@ export interface OpenAIOptionsInput {
|
|||||||
readonly promptCacheKey?: string
|
readonly promptCacheKey?: string
|
||||||
readonly reasoningEffort?: ReasoningEffort
|
readonly reasoningEffort?: ReasoningEffort
|
||||||
readonly reasoningSummary?: "auto"
|
readonly reasoningSummary?: "auto"
|
||||||
readonly includeEncryptedReasoning?: boolean
|
// OpenAI Responses `include` wire field. Mirrors the official SDK's
|
||||||
|
// `ResponseIncludable[]` union exactly so AI SDK callers and direct
|
||||||
|
// native-SDK callers share one shape and no translation is required.
|
||||||
|
readonly include?: ReadonlyArray<OpenAIResponseIncludable>
|
||||||
readonly textVerbosity?: TextVerbosity
|
readonly textVerbosity?: TextVerbosity
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +31,7 @@ const openAIProviderOptions = (options: OpenAIOptionsInput | undefined): Provide
|
|||||||
promptCacheKey: options?.promptCacheKey,
|
promptCacheKey: options?.promptCacheKey,
|
||||||
reasoningEffort: options?.reasoningEffort,
|
reasoningEffort: options?.reasoningEffort,
|
||||||
reasoningSummary: options?.reasoningSummary,
|
reasoningSummary: options?.reasoningSummary,
|
||||||
includeEncryptedReasoning: options?.includeEncryptedReasoning,
|
include: options?.include,
|
||||||
textVerbosity: options?.textVerbosity,
|
textVerbosity: options?.textVerbosity,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -42,6 +48,12 @@ export const gpt5DefaultOptions = (
|
|||||||
return openAIProviderOptions({
|
return openAIProviderOptions({
|
||||||
reasoningEffort: "medium",
|
reasoningEffort: "medium",
|
||||||
reasoningSummary: "auto",
|
reasoningSummary: "auto",
|
||||||
|
// GPT-5 reasoning models are configured stateless (`store: false`) by
|
||||||
|
// `openAIDefaultOptions` below, so the only way a follow-up turn can
|
||||||
|
// carry reasoning state is via the encrypted reasoning include. Without
|
||||||
|
// this, callers using the default model facade get reasoning summaries
|
||||||
|
// they cannot replay statelessly.
|
||||||
|
include: ["reasoning.encrypted_content"],
|
||||||
textVerbosity:
|
textVerbosity:
|
||||||
options.textVerbosity === true && id.includes("gpt-5.") && !id.includes("codex") && !id.includes("-chat")
|
options.textVerbosity === true && id.includes("gpt-5.") && !id.includes("codex") && !id.includes("-chat")
|
||||||
? "low"
|
? "low"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import * as OpenAIChat from "../protocols/openai-chat"
|
|||||||
import * as OpenAIResponses from "../protocols/openai-responses"
|
import * as OpenAIResponses from "../protocols/openai-responses"
|
||||||
import { withOpenAIOptions, type OpenAIProviderOptionsInput } from "./openai-options"
|
import { withOpenAIOptions, type OpenAIProviderOptionsInput } from "./openai-options"
|
||||||
|
|
||||||
export type { OpenAIOptionsInput } from "./openai-options"
|
export type { OpenAIOptionsInput, OpenAIResponseIncludable } from "./openai-options"
|
||||||
|
|
||||||
export const id = ProviderID.make("openai")
|
export const id = ProviderID.make("openai")
|
||||||
|
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ function makeFromTransport<Body, Prepared, Frame, Event, State>(
|
|||||||
)
|
)
|
||||||
return events.pipe(
|
return events.pipe(
|
||||||
Stream.mapAccumEffect(
|
Stream.mapAccumEffect(
|
||||||
protocol.stream.initial,
|
() => protocol.stream.initial(request),
|
||||||
protocol.stream.step,
|
protocol.stream.step,
|
||||||
protocol.stream.onHalt ? { onHalt: protocol.stream.onHalt } : undefined,
|
protocol.stream.onHalt ? { onHalt: protocol.stream.onHalt } : undefined,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ export interface ProtocolBody<Body> {
|
|||||||
export interface ProtocolStream<Frame, Event, State> {
|
export interface ProtocolStream<Frame, Event, State> {
|
||||||
/** Schema for one decoded streaming event, decoded from a transport frame. */
|
/** Schema for one decoded streaming event, decoded from a transport frame. */
|
||||||
readonly event: Schema.Codec<Event, Frame>
|
readonly event: Schema.Codec<Event, Frame>
|
||||||
/** Initial parser state. Called once per response. */
|
/** Initial parser state. Called once per response with the resolved request. */
|
||||||
readonly initial: () => State
|
readonly initial: (request: LLMRequest) => State
|
||||||
/** Translate one event into emitted `LLMEvent`s plus the next state. */
|
/** Translate one event into emitted `LLMEvent`s plus the next state. */
|
||||||
readonly step: (state: State, event: Event) => Effect.Effect<readonly [State, ReadonlyArray<LLMEvent>], LLMError>
|
readonly step: (state: State, event: Event) => Effect.Effect<readonly [State, ReadonlyArray<LLMEvent>], LLMError>
|
||||||
/** Optional request-completion signal for transports that do not end naturally. */
|
/** Optional request-completion signal for transports that do not end naturally. */
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export function continuationRequest(input: {
|
|||||||
tools: features.has("tool-call") ? [continuationTool] : [],
|
tools: features.has("tool-call") ? [continuationTool] : [],
|
||||||
cache: "none",
|
cache: "none",
|
||||||
providerOptions: features.has("encrypted-reasoning")
|
providerOptions: features.has("encrypted-reasoning")
|
||||||
? { openai: { store: false, includeEncryptedReasoning: true, reasoningSummary: "auto" } }
|
? { openai: { store: false, include: ["reasoning.encrypted_content"], reasoningSummary: "auto" } }
|
||||||
: undefined,
|
: undefined,
|
||||||
generation: { maxTokens: 80, temperature: 0 },
|
generation: { maxTokens: 80, temperature: 0 },
|
||||||
})
|
})
|
||||||
|
|||||||
+3
-3
File diff suppressed because one or more lines are too long
+4
-4
File diff suppressed because one or more lines are too long
Vendored
+3
-3
File diff suppressed because one or more lines are too long
@@ -209,80 +209,6 @@ describe("Anthropic Messages route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("lowers Anthropic thinking provider option (enabled)", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const prepared = yield* LLMClient.prepare(
|
|
||||||
LLM.request({
|
|
||||||
model,
|
|
||||||
prompt: "think",
|
|
||||||
providerOptions: { anthropic: { thinking: { type: "enabled", budgetTokens: 12345 } } },
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
expect(prepared.body).toMatchObject({ thinking: { type: "enabled", budget_tokens: 12345 } })
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.effect("lowers Anthropic adaptive thinking with effort sibling", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const prepared = yield* LLMClient.prepare(
|
|
||||||
LLM.request({
|
|
||||||
model,
|
|
||||||
prompt: "think",
|
|
||||||
providerOptions: { anthropic: { thinking: { type: "adaptive", display: "summarized" }, effort: "max" } },
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
expect(prepared.body).toMatchObject({
|
|
||||||
thinking: { type: "adaptive", display: "summarized" },
|
|
||||||
effort: "max",
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.effect("sets per-tool eager_input_streaming only when toolStreaming is true", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const off = yield* LLMClient.prepare<AnthropicMessages.AnthropicMessagesBody>(
|
|
||||||
LLM.request({
|
|
||||||
model,
|
|
||||||
prompt: "use tool",
|
|
||||||
providerOptions: { anthropic: { toolStreaming: false } },
|
|
||||||
tools: [{ name: "lookup", description: "lookup", inputSchema: { type: "object" } }],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
expect(off.body.tools?.[0]?.eager_input_streaming).toBeUndefined()
|
|
||||||
|
|
||||||
const on = yield* LLMClient.prepare<AnthropicMessages.AnthropicMessagesBody>(
|
|
||||||
LLM.request({
|
|
||||||
model,
|
|
||||||
prompt: "use tool",
|
|
||||||
providerOptions: { anthropic: { toolStreaming: true } },
|
|
||||||
tools: [{ name: "lookup", description: "lookup", inputSchema: { type: "object" } }],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
expect(on.body.tools?.[0]?.eager_input_streaming).toBe(true)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.effect("passes unknown Anthropic provider options through with snake-cased keys", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const prepared = yield* LLMClient.prepare(
|
|
||||||
LLM.request({
|
|
||||||
model,
|
|
||||||
prompt: "go",
|
|
||||||
providerOptions: {
|
|
||||||
anthropic: {
|
|
||||||
anthropicBeta: ["claude-2024-07-15"],
|
|
||||||
customField: { keepCamelCase: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
expect(prepared.body).toMatchObject({
|
|
||||||
anthropic_beta: ["claude-2024-07-15"],
|
|
||||||
custom_field: { keepCamelCase: true },
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.effect("lowers preserved Anthropic reasoning signature metadata", () =>
|
it.effect("lowers preserved Anthropic reasoning signature metadata", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const prepared = yield* LLMClient.prepare(
|
const prepared = yield* LLMClient.prepare(
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Auth, LLMClient } from "../../src/route"
|
|||||||
import * as OpenAICompatible from "../../src/providers/openai-compatible"
|
import * as OpenAICompatible from "../../src/providers/openai-compatible"
|
||||||
import * as OpenAICompatibleChat from "../../src/protocols/openai-compatible-chat"
|
import * as OpenAICompatibleChat from "../../src/protocols/openai-compatible-chat"
|
||||||
import { it } from "../lib/effect"
|
import { it } from "../lib/effect"
|
||||||
import { dynamicResponse, fixedResponse } from "../lib/http"
|
import { dynamicResponse } from "../lib/http"
|
||||||
import { sseEvents } from "../lib/sse"
|
import { sseEvents } from "../lib/sse"
|
||||||
|
|
||||||
const Json = Schema.fromJsonString(Schema.Unknown)
|
const Json = Schema.fromJsonString(Schema.Unknown)
|
||||||
@@ -199,134 +199,6 @@ describe("OpenAI-compatible Chat route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("passes through compatible options and prior reasoning for tool continuations", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const prepared = yield* LLMClient.prepare(
|
|
||||||
LLM.request({
|
|
||||||
model,
|
|
||||||
providerOptions: {
|
|
||||||
deepseek: {
|
|
||||||
reasoningEffort: "max",
|
|
||||||
textVerbosity: "low",
|
|
||||||
promptCacheKey: "session_123",
|
|
||||||
strictJsonSchema: false,
|
|
||||||
enable_thinking: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
messages: [
|
|
||||||
Message.user("Audit the site"),
|
|
||||||
Message.make({
|
|
||||||
role: "assistant",
|
|
||||||
native: { openaiCompatible: { reasoning_content: "I should inspect the page." } },
|
|
||||||
content: [ToolCallPart.make({ id: "call_1", name: "lookup", input: { query: "page" } })],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(prepared.body).toMatchObject({
|
|
||||||
reasoning_effort: "max",
|
|
||||||
verbosity: "low",
|
|
||||||
prompt_cache_key: "session_123",
|
|
||||||
enable_thinking: true,
|
|
||||||
messages: [
|
|
||||||
{ role: "user", content: "Audit the site" },
|
|
||||||
{
|
|
||||||
role: "assistant",
|
|
||||||
reasoning_content: "I should inspect the page.",
|
|
||||||
tool_calls: [
|
|
||||||
{
|
|
||||||
id: "call_1",
|
|
||||||
function: { name: "lookup", arguments: '{"query":"page"}' },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
expect(prepared.body).not.toHaveProperty("strictJsonSchema")
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.effect("preserves structured reasoning_details on compatible continuations", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const details = [
|
|
||||||
{ type: "reasoning.text", text: "Let me work through this.", format: "anthropic-claude-v1", index: 0 },
|
|
||||||
{ type: "reasoning.encrypted", data: "sha256:abc123", format: "anthropic-claude-v1", index: 1 },
|
|
||||||
]
|
|
||||||
const prepared = yield* LLMClient.prepare(
|
|
||||||
LLM.request({
|
|
||||||
model,
|
|
||||||
messages: [
|
|
||||||
Message.make({
|
|
||||||
role: "assistant",
|
|
||||||
native: { openaiCompatible: { reasoning_details: details } },
|
|
||||||
content: [ToolCallPart.make({ id: "call_1", name: "lookup", input: {} })],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(prepared.body).toMatchObject({
|
|
||||||
messages: [{ role: "assistant", reasoning_details: details }],
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.effect("resolves dot-scoped compatible provider options", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const prepared = yield* LLMClient.prepare(
|
|
||||||
LLM.request({
|
|
||||||
model: OpenAICompatibleChat.route
|
|
||||||
.with({ provider: "opencode.internal", endpoint: { baseURL: "https://api.example.test/v1" } })
|
|
||||||
.model({ id: "reasoning-model" }),
|
|
||||||
prompt: "Think.",
|
|
||||||
providerOptions: { opencode: { reasoningEffort: "max", enable_thinking: true } },
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(prepared.body).toMatchObject({ reasoning_effort: "max", enable_thinking: true })
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.effect("does not apply OpenAI effort limits to compatible providers", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const prepared = yield* LLMClient.prepare(
|
|
||||||
LLM.request({
|
|
||||||
model: OpenAICompatibleChat.route
|
|
||||||
.with({ provider: "openai", endpoint: { baseURL: "https://compatible.example.test/v1" } })
|
|
||||||
.model({ id: "reasoning-model" }),
|
|
||||||
prompt: "Think.",
|
|
||||||
providerOptions: { openai: { reasoningEffort: "max" } },
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(prepared.body).toMatchObject({ reasoning_effort: "max" })
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.effect("parses compatible reasoning field variants", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const response = yield* LLMClient.generate(request).pipe(
|
|
||||||
Effect.provide(
|
|
||||||
fixedResponse(
|
|
||||||
sseEvents(
|
|
||||||
deltaChunk({ reasoning: "fallback" }),
|
|
||||||
deltaChunk({
|
|
||||||
reasoning_details: [
|
|
||||||
{ type: "reasoning.text", text: " text-detail", format: "anthropic-claude-v1", index: 0 },
|
|
||||||
{ type: "reasoning.summary", summary: " summary-detail", format: "anthropic-claude-v1", index: 1 },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
deltaChunk({}, "stop"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(response.reasoning).toBe("fallback text-detail summary-detail")
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.effect("posts to the configured compatible endpoint and parses text usage", () =>
|
it.effect("posts to the configured compatible endpoint and parses text usage", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const response = yield* LLMClient.generate(request).pipe(
|
const response = yield* LLMClient.generate(request).pipe(
|
||||||
|
|||||||
@@ -393,7 +393,7 @@ describe("OpenAI Responses route", () => {
|
|||||||
promptCacheKey: "session_123",
|
promptCacheKey: "session_123",
|
||||||
reasoningEffort: "high",
|
reasoningEffort: "high",
|
||||||
reasoningSummary: "auto",
|
reasoningSummary: "auto",
|
||||||
includeEncryptedReasoning: true,
|
include: ["reasoning.encrypted_content"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -407,27 +407,105 @@ describe("OpenAI Responses route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("passes unknown OpenAI provider options through with snake-cased keys", () =>
|
it.effect("accepts the full ResponseIncludable union", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const prepared = yield* LLMClient.prepare<OpenAIResponses.OpenAIResponsesBody>(
|
const prepared = yield* LLMClient.prepare<OpenAIResponses.OpenAIResponsesBody>(
|
||||||
LLM.request({
|
LLM.request({
|
||||||
model: OpenAI.configure({ baseURL: "https://api.openai.test/v1/", apiKey: "test" }).model("gpt-5.2"),
|
model,
|
||||||
prompt: "passthrough",
|
prompt: "hi",
|
||||||
providerOptions: {
|
providerOptions: {
|
||||||
openai: {
|
openai: {
|
||||||
customCamelCaseField: "value",
|
include: ["reasoning.encrypted_content", "code_interpreter_call.outputs", "web_search_call.results"],
|
||||||
already_snake_case: 42,
|
|
||||||
nested: { keepCamelCase: true },
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(prepared.body).toMatchObject({
|
expect(prepared.body.include).toEqual([
|
||||||
custom_camel_case_field: "value",
|
"reasoning.encrypted_content",
|
||||||
already_snake_case: 42,
|
"code_interpreter_call.outputs",
|
||||||
nested: { keepCamelCase: true },
|
"web_search_call.results",
|
||||||
})
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("filters unknown includable values out of the include array", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const prepared = yield* LLMClient.prepare<OpenAIResponses.OpenAIResponsesBody>(
|
||||||
|
LLM.request({
|
||||||
|
model,
|
||||||
|
prompt: "hi",
|
||||||
|
// The user passed one invalid entry alongside a valid one. Keep the
|
||||||
|
// valid one so the request still succeeds rather than failing on a
|
||||||
|
// typo from upstream config.
|
||||||
|
providerOptions: { openai: { include: ["reasoning.encrypted_content", "bogus.thing"] } },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prepared.body.include).toEqual(["reasoning.encrypted_content"])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("treats an explicit empty include as no include at all", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const prepared = yield* LLMClient.prepare<OpenAIResponses.OpenAIResponsesBody>(
|
||||||
|
LLM.request({ model, prompt: "hi", providerOptions: { openai: { include: [] } } }),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prepared.body.include).toBeUndefined()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("treats an all-invalid include as no include at all", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const prepared = yield* LLMClient.prepare<OpenAIResponses.OpenAIResponsesBody>(
|
||||||
|
LLM.request({ model, prompt: "hi", providerOptions: { openai: { include: ["bogus.thing"] } } }),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prepared.body.include).toBeUndefined()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("omits include when no include is set", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const prepared = yield* LLMClient.prepare<OpenAIResponses.OpenAIResponsesBody>(
|
||||||
|
LLM.request({ model, prompt: "hi", providerOptions: { openai: { store: false } } }),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prepared.body.include).toBeUndefined()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("requests encrypted reasoning by default for GPT-5 reasoning models", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
// The native OpenAI facade configures GPT-5 stateless (store: false) with
|
||||||
|
// reasoningSummary: "auto" by default. Without `include`, a follow-up
|
||||||
|
// turn cannot replay reasoning state, so the facade also opts into
|
||||||
|
// `reasoning.encrypted_content` automatically.
|
||||||
|
const prepared = yield* LLMClient.prepare<OpenAIResponses.OpenAIResponsesBody>(
|
||||||
|
LLM.request({
|
||||||
|
model: OpenAI.configure({ baseURL: "https://api.openai.test/v1/", apiKey: "test" }).responses("gpt-5.2"),
|
||||||
|
prompt: "hi",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prepared.body.store).toBe(false)
|
||||||
|
expect(prepared.body.include).toEqual(["reasoning.encrypted_content"])
|
||||||
|
expect(prepared.body.reasoning).toEqual({ effort: "medium", summary: "auto" })
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("lets callers opt out of the GPT-5 default include", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const prepared = yield* LLMClient.prepare<OpenAIResponses.OpenAIResponsesBody>(
|
||||||
|
LLM.request({
|
||||||
|
model: OpenAI.configure({ baseURL: "https://api.openai.test/v1/", apiKey: "test" }).responses("gpt-5.2"),
|
||||||
|
prompt: "hi",
|
||||||
|
providerOptions: { openai: { include: [] } },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prepared.body.include).toBeUndefined()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -571,6 +649,94 @@ describe("OpenAI Responses route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("streams each reasoning summary part as a separate block", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const response = yield* LLMClient.generate(
|
||||||
|
LLM.updateRequest(request, { providerOptions: { openai: { store: false } } }),
|
||||||
|
).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
fixedResponse(
|
||||||
|
sseEvents(
|
||||||
|
{
|
||||||
|
type: "response.output_item.added",
|
||||||
|
item: { type: "reasoning", id: "rs_1", encrypted_content: null },
|
||||||
|
},
|
||||||
|
{ type: "response.reasoning_summary_part.added", item_id: "rs_1", summary_index: 0 },
|
||||||
|
{ type: "response.reasoning_summary_text.delta", item_id: "rs_1", summary_index: 0, delta: "First" },
|
||||||
|
{ type: "response.reasoning_summary_part.done", item_id: "rs_1", summary_index: 0 },
|
||||||
|
{ type: "response.reasoning_summary_part.added", item_id: "rs_1", summary_index: 1 },
|
||||||
|
{ type: "response.reasoning_summary_text.delta", item_id: "rs_1", summary_index: 1, delta: "Second" },
|
||||||
|
{ type: "response.reasoning_summary_part.done", item_id: "rs_1", summary_index: 1 },
|
||||||
|
{
|
||||||
|
type: "response.output_item.done",
|
||||||
|
item: { type: "reasoning", id: "rs_1", encrypted_content: "encrypted-state" },
|
||||||
|
},
|
||||||
|
{ type: "response.completed", response: { id: "resp_1" } },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.reasoning).toBe("FirstSecond")
|
||||||
|
expect(response.events).toMatchObject([
|
||||||
|
{ type: "step-start", index: 0 },
|
||||||
|
{
|
||||||
|
type: "reasoning-start",
|
||||||
|
id: "rs_1:0",
|
||||||
|
providerMetadata: { openai: { itemId: "rs_1", reasoningEncryptedContent: null } },
|
||||||
|
},
|
||||||
|
{ type: "reasoning-delta", id: "rs_1:0", text: "First" },
|
||||||
|
{ type: "reasoning-end", id: "rs_1:0", providerMetadata: { openai: { itemId: "rs_1" } } },
|
||||||
|
{
|
||||||
|
type: "reasoning-start",
|
||||||
|
id: "rs_1:1",
|
||||||
|
providerMetadata: { openai: { itemId: "rs_1", reasoningEncryptedContent: null } },
|
||||||
|
},
|
||||||
|
{ type: "reasoning-delta", id: "rs_1:1", text: "Second" },
|
||||||
|
{
|
||||||
|
type: "reasoning-end",
|
||||||
|
id: "rs_1:1",
|
||||||
|
providerMetadata: { openai: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" } },
|
||||||
|
},
|
||||||
|
{ type: "step-finish", index: 0, reason: "stop" },
|
||||||
|
{ type: "finish", reason: "stop" },
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("closes reasoning summary parts when storage is not disabled", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const response = yield* LLMClient.generate(request).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
fixedResponse(
|
||||||
|
sseEvents(
|
||||||
|
{
|
||||||
|
type: "response.output_item.added",
|
||||||
|
item: { type: "reasoning", id: "rs_1", encrypted_content: null },
|
||||||
|
},
|
||||||
|
{ type: "response.reasoning_summary_part.added", item_id: "rs_1", summary_index: 0 },
|
||||||
|
{ type: "response.reasoning_summary_text.delta", item_id: "rs_1", summary_index: 0, delta: "First" },
|
||||||
|
{ type: "response.reasoning_summary_part.done", item_id: "rs_1", summary_index: 0 },
|
||||||
|
{ type: "response.reasoning_summary_part.added", item_id: "rs_1", summary_index: 1 },
|
||||||
|
{ type: "response.reasoning_summary_text.delta", item_id: "rs_1", summary_index: 1, delta: "Second" },
|
||||||
|
{ type: "response.reasoning_summary_part.done", item_id: "rs_1", summary_index: 1 },
|
||||||
|
{
|
||||||
|
type: "response.output_item.done",
|
||||||
|
item: { type: "reasoning", id: "rs_1", encrypted_content: null },
|
||||||
|
},
|
||||||
|
{ type: "response.completed", response: { id: "resp_1" } },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.events.filter((event) => event.type === "reasoning-end")).toEqual([
|
||||||
|
{ type: "reasoning-end", id: "rs_1:0", providerMetadata: { openai: { itemId: "rs_1" } } },
|
||||||
|
{ type: "reasoning-end", id: "rs_1:1", providerMetadata: { openai: { itemId: "rs_1" } } },
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("continues a stateless reasoning conversation", () =>
|
it.effect("continues a stateless reasoning conversation", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const response = yield* LLMClient.generate(
|
const response = yield* LLMClient.generate(
|
||||||
@@ -594,6 +760,7 @@ describe("OpenAI Responses route", () => {
|
|||||||
]),
|
]),
|
||||||
Message.user("Summarize it."),
|
Message.user("Summarize it."),
|
||||||
],
|
],
|
||||||
|
providerOptions: { openai: { store: false } },
|
||||||
}),
|
}),
|
||||||
).pipe(
|
).pipe(
|
||||||
Effect.provide(
|
Effect.provide(
|
||||||
@@ -651,6 +818,7 @@ describe("OpenAI Responses route", () => {
|
|||||||
{ type: "text", text: "After." },
|
{ type: "text", text: "After." },
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
|
providerOptions: { openai: { store: false } },
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -667,6 +835,66 @@ describe("OpenAI Responses route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("references stored reasoning items by id", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const prepared = yield* LLMClient.prepare<OpenAIResponses.OpenAIResponsesBody>(
|
||||||
|
LLM.request({
|
||||||
|
model,
|
||||||
|
messages: [
|
||||||
|
Message.assistant([
|
||||||
|
{
|
||||||
|
type: "reasoning",
|
||||||
|
text: "Checked the previous diff.",
|
||||||
|
providerMetadata: { openai: { itemId: "rs_1" } },
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
providerOptions: { openai: { store: true } },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prepared.body.input).toEqual([{ type: "item_reference", id: "rs_1" }])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("joins streamed summary blocks into one continuation reasoning item", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const prepared = yield* LLMClient.prepare<OpenAIResponses.OpenAIResponsesBody>(
|
||||||
|
LLM.request({
|
||||||
|
id: "req_multi_summary_continuation",
|
||||||
|
model,
|
||||||
|
messages: [
|
||||||
|
Message.assistant([
|
||||||
|
{
|
||||||
|
type: "reasoning",
|
||||||
|
text: "First",
|
||||||
|
providerMetadata: { openai: { itemId: "rs_1" } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "reasoning",
|
||||||
|
text: "Second",
|
||||||
|
providerMetadata: { openai: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" } },
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
providerOptions: { openai: { store: false } },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prepared.body.input).toEqual([
|
||||||
|
{
|
||||||
|
type: "reasoning",
|
||||||
|
id: "rs_1",
|
||||||
|
encrypted_content: "encrypted-state",
|
||||||
|
summary: [
|
||||||
|
{ type: "summary_text", text: "First" },
|
||||||
|
{ type: "summary_text", text: "Second" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("skips non-persisted reasoning ids without encrypted state", () =>
|
it.effect("skips non-persisted reasoning ids without encrypted state", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const prepared = yield* LLMClient.prepare(
|
const prepared = yield* LLMClient.prepare(
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ const normalizeImageText = (value: string) =>
|
|||||||
const encryptedReasoningOptions = {
|
const encryptedReasoningOptions = {
|
||||||
openai: {
|
openai: {
|
||||||
store: false,
|
store: false,
|
||||||
includeEncryptedReasoning: true,
|
include: ["reasoning.encrypted_content"],
|
||||||
reasoningEffort: "low",
|
reasoningEffort: "low",
|
||||||
reasoningSummary: "auto",
|
reasoningSummary: "auto",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { GenerationOptions, LLM, LLMEvent, LLMRequest, LLMResponse, ToolChoice }
|
|||||||
import { Auth, LLMClient } from "../src/route"
|
import { Auth, LLMClient } from "../src/route"
|
||||||
import * as AnthropicMessages from "../src/protocols/anthropic-messages"
|
import * as AnthropicMessages from "../src/protocols/anthropic-messages"
|
||||||
import * as OpenAIChat from "../src/protocols/openai-chat"
|
import * as OpenAIChat from "../src/protocols/openai-chat"
|
||||||
|
import * as OpenAIResponses from "../src/protocols/openai-responses"
|
||||||
import { tool, ToolFailure, type ToolExecuteContext } from "../src/tool"
|
import { tool, ToolFailure, type ToolExecuteContext } from "../src/tool"
|
||||||
import { ToolRuntime } from "../src/tool-runtime"
|
import { ToolRuntime } from "../src/tool-runtime"
|
||||||
import { it } from "./lib/effect"
|
import { it } from "./lib/effect"
|
||||||
@@ -309,6 +310,80 @@ describe("LLMClient tools", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("replays encrypted OpenAI reasoning items with tool outputs", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const bodies: unknown[] = []
|
||||||
|
const layer = dynamicResponse((input) =>
|
||||||
|
Effect.sync(() => {
|
||||||
|
bodies.push(decodeJson(input.text))
|
||||||
|
return input.respond(
|
||||||
|
bodies.length === 1
|
||||||
|
? sseEvents(
|
||||||
|
{
|
||||||
|
type: "response.output_item.added",
|
||||||
|
item: { type: "reasoning", id: "rs_1", encrypted_content: null },
|
||||||
|
},
|
||||||
|
{ type: "response.reasoning_summary_part.added", item_id: "rs_1", summary_index: 0 },
|
||||||
|
{ type: "response.reasoning_summary_part.done", item_id: "rs_1", summary_index: 0 },
|
||||||
|
{
|
||||||
|
type: "response.output_item.done",
|
||||||
|
item: { type: "reasoning", id: "rs_1", encrypted_content: "encrypted-state" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "response.output_item.added",
|
||||||
|
item: {
|
||||||
|
type: "function_call",
|
||||||
|
id: "item_1",
|
||||||
|
call_id: "call_1",
|
||||||
|
name: "get_weather",
|
||||||
|
arguments: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ type: "response.function_call_arguments.delta", item_id: "item_1", delta: '{"city":"Paris"}' },
|
||||||
|
{
|
||||||
|
type: "response.output_item.done",
|
||||||
|
item: {
|
||||||
|
type: "function_call",
|
||||||
|
id: "item_1",
|
||||||
|
call_id: "call_1",
|
||||||
|
name: "get_weather",
|
||||||
|
arguments: '{"city":"Paris"}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ type: "response.completed", response: {} },
|
||||||
|
)
|
||||||
|
: sseEvents(
|
||||||
|
{ type: "response.output_text.delta", item_id: "msg_1", delta: "Done." },
|
||||||
|
{ type: "response.completed", response: {} },
|
||||||
|
),
|
||||||
|
{ headers: { "content-type": "text/event-stream" } },
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
yield* TestToolRuntime.runTools({
|
||||||
|
request: LLM.request({
|
||||||
|
model: OpenAIResponses.route
|
||||||
|
.with({ endpoint: { baseURL: "https://api.openai.test/v1/" }, auth: Auth.bearer("test") })
|
||||||
|
.model({ id: "gpt-5.5" }),
|
||||||
|
prompt: "Use the tool.",
|
||||||
|
providerOptions: { openai: { store: false, include: ["reasoning.encrypted_content"] } },
|
||||||
|
}),
|
||||||
|
tools: { get_weather },
|
||||||
|
}).pipe(Stream.runCollect, Effect.provide(layer))
|
||||||
|
|
||||||
|
expect(bodies[1]).toMatchObject({
|
||||||
|
include: ["reasoning.encrypted_content"],
|
||||||
|
input: [
|
||||||
|
{ role: "user" },
|
||||||
|
{ type: "reasoning", id: "rs_1", summary: [], encrypted_content: "encrypted-state" },
|
||||||
|
{ type: "function_call", call_id: "call_1", name: "get_weather" },
|
||||||
|
{ type: "function_call_output", call_id: "call_1" },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("emits tool-error for unknown tools so the model can self-correct", () =>
|
it.effect("emits tool-error for unknown tools so the model can self-correct", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const layer = scriptedResponses([
|
const layer = scriptedResponses([
|
||||||
|
|||||||
@@ -74,15 +74,15 @@
|
|||||||
"@actions/github": "6.0.1",
|
"@actions/github": "6.0.1",
|
||||||
"@agentclientprotocol/sdk": "0.21.0",
|
"@agentclientprotocol/sdk": "0.21.0",
|
||||||
"@ai-sdk/alibaba": "1.0.17",
|
"@ai-sdk/alibaba": "1.0.17",
|
||||||
"@ai-sdk/amazon-bedrock": "4.0.96",
|
"@ai-sdk/amazon-bedrock": "4.0.107",
|
||||||
"@ai-sdk/anthropic": "3.0.71",
|
"@ai-sdk/anthropic": "3.0.71",
|
||||||
"@ai-sdk/azure": "3.0.49",
|
"@ai-sdk/azure": "3.0.49",
|
||||||
"@ai-sdk/cerebras": "2.0.41",
|
"@ai-sdk/cerebras": "2.0.41",
|
||||||
"@ai-sdk/cohere": "3.0.27",
|
"@ai-sdk/cohere": "3.0.27",
|
||||||
"@ai-sdk/deepinfra": "2.0.41",
|
"@ai-sdk/deepinfra": "2.0.41",
|
||||||
"@ai-sdk/gateway": "3.0.104",
|
"@ai-sdk/gateway": "3.0.104",
|
||||||
"@ai-sdk/google": "3.0.63",
|
"@ai-sdk/google": "3.0.75",
|
||||||
"@ai-sdk/google-vertex": "4.0.112",
|
"@ai-sdk/google-vertex": "4.0.131",
|
||||||
"@ai-sdk/groq": "3.0.31",
|
"@ai-sdk/groq": "3.0.31",
|
||||||
"@ai-sdk/mistral": "3.0.27",
|
"@ai-sdk/mistral": "3.0.27",
|
||||||
"@ai-sdk/openai": "3.0.53",
|
"@ai-sdk/openai": "3.0.53",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Cache, Clock, Duration, Effect, Layer, Option, Schema, SchemaGetter, Context } from "effect"
|
import { Cache, Clock, Duration, Effect, Layer, Option, Schema, SchemaGetter, Context } from "effect"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import {
|
import {
|
||||||
FetchHttpClient,
|
FetchHttpClient,
|
||||||
HttpClient,
|
HttpClient,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { eq } from "drizzle-orm"
|
import { eq } from "drizzle-orm"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import { Effect, Layer, Option, Schema, Context } from "effect"
|
import { Effect, Layer, Option, Schema, Context } from "effect"
|
||||||
|
|
||||||
import { Database } from "@/storage/db"
|
import { Database } from "@/storage/db"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Config } from "@/config/config"
|
import { Config } from "@/config/config"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import { Provider } from "@/provider/provider"
|
import { Provider } from "@/provider/provider"
|
||||||
import { ModelID, ProviderID } from "../provider/schema"
|
import { ModelID, ProviderID } from "../provider/schema"
|
||||||
import { generateObject, streamObject, type ModelMessage } from "ai"
|
import { generateObject, streamObject, type ModelMessage } from "ai"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { BusEvent } from "./bus-event"
|
|||||||
import { GlobalBus } from "./global"
|
import { GlobalBus } from "./global"
|
||||||
import { InstanceState } from "@/effect/instance-state"
|
import { InstanceState } from "@/effect/instance-state"
|
||||||
import { makeRuntime } from "@/effect/run-service"
|
import { makeRuntime } from "@/effect/run-service"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import { Identifier } from "@/id/id"
|
import { Identifier } from "@/id/id"
|
||||||
import type { InstanceContext } from "@/project/instance-context"
|
import type { InstanceContext } from "@/project/instance-context"
|
||||||
import { InstanceRef } from "@/effect/instance-ref"
|
import { InstanceRef } from "@/effect/instance-ref"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as Log from "@opencode-ai/core/util/log"
|
import * as Log from "@opencode-ai/core/util/log"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { pathToFileURL } from "url"
|
import { pathToFileURL } from "url"
|
||||||
import os from "os"
|
import os from "os"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Context, Effect, FiberMap, Iterable, Layer, Schema, Stream } from "effect"
|
import { Context, Effect, FiberMap, Iterable, Layer, Schema, Stream } from "effect"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import { FetchHttpClient, HttpBody, HttpClient, HttpClientError, HttpClientRequest } from "effect/unstable/http"
|
import { FetchHttpClient, HttpBody, HttpClient, HttpClientError, HttpClientRequest } from "effect/unstable/http"
|
||||||
import { Database } from "@/storage/db"
|
import { Database } from "@/storage/db"
|
||||||
import { asc } from "drizzle-orm"
|
import { asc } from "drizzle-orm"
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export class Service extends ConfigService.Service<Service>()("@opencode/Runtime
|
|||||||
experimentalIconDiscovery: enabledByExperimental("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY"),
|
experimentalIconDiscovery: enabledByExperimental("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY"),
|
||||||
outputTokenMax: positiveInteger("OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX"),
|
outputTokenMax: positiveInteger("OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX"),
|
||||||
bashDefaultTimeoutMs: positiveInteger("OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS"),
|
bashDefaultTimeoutMs: positiveInteger("OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS"),
|
||||||
experimentalNativeLlm: enabledByExperimental("OPENCODE_EXPERIMENTAL_NATIVE_LLM"),
|
experimentalNativeLlm: bool("OPENCODE_EXPERIMENTAL_NATIVE_LLM"),
|
||||||
client: Config.string("OPENCODE_CLIENT").pipe(Config.withDefault("cli")),
|
client: Config.string("OPENCODE_CLIENT").pipe(Config.withDefault("cli")),
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import { Context, Effect, Layer } from "effect"
|
import { Context, Effect, Layer } from "effect"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import { InstanceState } from "@/effect/instance-state"
|
import { InstanceState } from "@/effect/instance-state"
|
||||||
|
|
||||||
type State = Record<string, string | undefined>
|
type State = Record<string, string | undefined>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { BusEvent } from "@/bus/bus-event"
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import { InstanceState } from "@/effect/instance-state"
|
import { InstanceState } from "@/effect/instance-state"
|
||||||
|
|
||||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||||
import { Cause, Context, Effect, Fiber, Layer, Queue, Schema, Stream } from "effect"
|
import { Cause, Context, Effect, Fiber, Layer, Queue, Schema, Stream } from "effect"
|
||||||
import type { PlatformError } from "effect/PlatformError"
|
import type { PlatformError } from "effect/PlatformError"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Effect, Layer, Context, Schema } from "effect"
|
import { Effect, Layer, Context, Schema } from "effect"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import { ChildProcess } from "effect/unstable/process"
|
import { ChildProcess } from "effect/unstable/process"
|
||||||
import { AppProcess } from "@opencode-ai/core/process"
|
import { AppProcess } from "@opencode-ai/core/process"
|
||||||
import { InstanceState } from "@/effect/instance-state"
|
import { InstanceState } from "@/effect/instance-state"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Effect, Layer, Schema, Context, Stream } from "effect"
|
import { Effect, Layer, Schema, Context, Stream } from "effect"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||||
import { withTransientReadRetry } from "@/util/effect-http-client"
|
import { withTransientReadRetry } from "@/util/effect-http-client"
|
||||||
import { errorMessage } from "@/util/error"
|
import { errorMessage } from "@/util/error"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import { Global } from "@opencode-ai/core/global"
|
import { Global } from "@opencode-ai/core/global"
|
||||||
import { Effect, Layer, Context, Option, Schema } from "effect"
|
import { Effect, Layer, Context, Option, Schema } from "effect"
|
||||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { dynamicTool, type Tool, jsonSchema, type JSONSchema7 } from "ai"
|
import { dynamicTool, type Tool, jsonSchema, type JSONSchema7 } from "ai"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
|
||||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
|
||||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { GlobalBus } from "@/bus/global"
|
import { GlobalBus } from "@/bus/global"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import { WorkspaceContext } from "@/control-plane/workspace-context"
|
import { WorkspaceContext } from "@/control-plane/workspace-context"
|
||||||
import { InstanceRef } from "@/effect/instance-ref"
|
import { InstanceRef } from "@/effect/instance-ref"
|
||||||
import { disposeInstance as runDisposers } from "@/effect/instance-registry"
|
import { disposeInstance as runDisposers } from "@/effect/instance-registry"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { and } from "drizzle-orm"
|
import { and, eq, sql } from "drizzle-orm"
|
||||||
import { Database } from "@/storage/db"
|
import { Database } from "@/storage/db"
|
||||||
import { eq } from "drizzle-orm"
|
|
||||||
import { ProjectTable } from "./project.sql"
|
import { ProjectTable } from "./project.sql"
|
||||||
import { PermissionTable, SessionTable } from "../session/session.sql"
|
import { PermissionTable, SessionTable } from "../session/session.sql"
|
||||||
import { WorkspaceTable } from "../control-plane/workspace.sql"
|
import { WorkspaceTable } from "../control-plane/workspace.sql"
|
||||||
@@ -20,7 +19,7 @@ import { AppProcess } from "@opencode-ai/core/process"
|
|||||||
import { Project as ProjectV2 } from "@opencode-ai/core/project"
|
import { Project as ProjectV2 } from "@opencode-ai/core/project"
|
||||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||||
import { AbsolutePath, NonNegativeInt, optionalOmitUndefined } from "@opencode-ai/core/schema"
|
import { AbsolutePath, NonNegativeInt, optionalOmitUndefined } from "@opencode-ai/core/schema"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
|
|
||||||
const log = Log.create({ service: "project" })
|
const log = Log.create({ service: "project" })
|
||||||
@@ -220,7 +219,10 @@ export const layer = Layer.effect(
|
|||||||
d.update(PermissionTable).set({ project_id: newID }).where(eq(PermissionTable.project_id, oldID)).run()
|
d.update(PermissionTable).set({ project_id: newID }).where(eq(PermissionTable.project_id, oldID)).run()
|
||||||
}
|
}
|
||||||
|
|
||||||
d.update(SessionTable).set({ project_id: newID }).where(eq(SessionTable.project_id, oldID)).run()
|
d.update(SessionTable)
|
||||||
|
.set({ project_id: newID, time_updated: sql`${SessionTable.time_updated}` })
|
||||||
|
.where(eq(SessionTable.project_id, oldID))
|
||||||
|
.run()
|
||||||
d.update(WorkspaceTable).set({ project_id: newID }).where(eq(WorkspaceTable.project_id, oldID)).run()
|
d.update(WorkspaceTable).set({ project_id: newID }).where(eq(WorkspaceTable.project_id, oldID)).run()
|
||||||
|
|
||||||
if (oldProject) d.delete(ProjectTable).where(eq(ProjectTable.id, oldID)).run()
|
if (oldProject) d.delete(ProjectTable).where(eq(ProjectTable.id, oldID)).run()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { AuthOAuthResult, Hooks } from "@opencode-ai/plugin"
|
import type { AuthOAuthResult, Hooks } from "@opencode-ai/plugin"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import { Auth } from "@/auth"
|
import { Auth } from "@/auth"
|
||||||
import { InstanceState } from "@/effect/instance-state"
|
import { InstanceState } from "@/effect/instance-state"
|
||||||
import { optionalOmitUndefined } from "@opencode-ai/core/schema"
|
import { optionalOmitUndefined } from "@opencode-ai/core/schema"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as Log from "@opencode-ai/core/util/log"
|
|||||||
import { Npm } from "@opencode-ai/core/npm"
|
import { Npm } from "@opencode-ai/core/npm"
|
||||||
import { Hash } from "@opencode-ai/core/util/hash"
|
import { Hash } from "@opencode-ai/core/util/hash"
|
||||||
import { Plugin } from "../plugin"
|
import { Plugin } from "../plugin"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import { type LanguageModelV3 } from "@ai-sdk/provider"
|
import { type LanguageModelV3 } from "@ai-sdk/provider"
|
||||||
import * as ModelsDev from "@opencode-ai/core/models-dev"
|
import * as ModelsDev from "@opencode-ai/core/models-dev"
|
||||||
import { Auth } from "../auth"
|
import { Auth } from "../auth"
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ function mimeToModality(mime: string): Modality | undefined {
|
|||||||
|
|
||||||
export const OUTPUT_TOKEN_MAX = 32_000
|
export const OUTPUT_TOKEN_MAX = 32_000
|
||||||
|
|
||||||
|
// OpenAI Responses `include` value that returns the encrypted reasoning state
|
||||||
|
// needed for stateless multi-turn reasoning (store: false). Hoisted so every
|
||||||
|
// branch that requests it stays in lockstep.
|
||||||
|
const INCLUDE_ENCRYPTED_REASONING = ["reasoning.encrypted_content"] as const
|
||||||
|
|
||||||
export function sanitizeSurrogates(content: string) {
|
export function sanitizeSurrogates(content: string) {
|
||||||
return content.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD")
|
return content.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD")
|
||||||
}
|
}
|
||||||
@@ -756,7 +761,7 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
|
|||||||
{
|
{
|
||||||
reasoningEffort: effort,
|
reasoningEffort: effort,
|
||||||
reasoningSummary: "auto",
|
reasoningSummary: "auto",
|
||||||
include: ["reasoning.encrypted_content"],
|
include: INCLUDE_ENCRYPTED_REASONING,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
@@ -790,7 +795,7 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
|
|||||||
{
|
{
|
||||||
reasoningEffort: effort,
|
reasoningEffort: effort,
|
||||||
reasoningSummary: "auto",
|
reasoningSummary: "auto",
|
||||||
include: ["reasoning.encrypted_content"],
|
include: INCLUDE_ENCRYPTED_REASONING,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
@@ -803,7 +808,7 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
|
|||||||
{
|
{
|
||||||
reasoningEffort: effort,
|
reasoningEffort: effort,
|
||||||
reasoningSummary: "auto",
|
reasoningSummary: "auto",
|
||||||
include: ["reasoning.encrypted_content"],
|
include: INCLUDE_ENCRYPTED_REASONING,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
@@ -1134,6 +1139,9 @@ export function options(input: {
|
|||||||
if (!input.model.api.id.includes("gpt-5-pro")) {
|
if (!input.model.api.id.includes("gpt-5-pro")) {
|
||||||
result["reasoningEffort"] = "medium"
|
result["reasoningEffort"] = "medium"
|
||||||
result["reasoningSummary"] = "auto"
|
result["reasoningSummary"] = "auto"
|
||||||
|
if (input.model.api.npm === "@ai-sdk/openai") {
|
||||||
|
result["include"] = INCLUDE_ENCRYPTED_REASONING
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only set textVerbosity for non-chat gpt-5.x models
|
// Only set textVerbosity for non-chat gpt-5.x models
|
||||||
@@ -1149,7 +1157,7 @@ export function options(input: {
|
|||||||
|
|
||||||
if (input.model.providerID.startsWith("opencode")) {
|
if (input.model.providerID.startsWith("opencode")) {
|
||||||
result["promptCacheKey"] = input.sessionID
|
result["promptCacheKey"] = input.sessionID
|
||||||
result["include"] = ["reasoning.encrypted_content"]
|
result["include"] = INCLUDE_ENCRYPTED_REASONING
|
||||||
result["reasoningSummary"] = "auto"
|
result["reasoningSummary"] = "auto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { Effect, Layer, Context, Schema } from "effect"
|
|||||||
import * as DateTime from "effect/DateTime"
|
import * as DateTime from "effect/DateTime"
|
||||||
import { InstanceState } from "@/effect/instance-state"
|
import { InstanceState } from "@/effect/instance-state"
|
||||||
import { isOverflow as overflow, usable } from "./overflow"
|
import { isOverflow as overflow, usable } from "./overflow"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||||
import { SessionEvent } from "@opencode-ai/core/session-event"
|
import { SessionEvent } from "@opencode-ai/core/session-event"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Provider } from "@/provider/provider"
|
import { Provider } from "@/provider/provider"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import * as Log from "@opencode-ai/core/util/log"
|
import * as Log from "@opencode-ai/core/util/log"
|
||||||
import { Context, Effect, Layer } from "effect"
|
import { Context, Effect, Layer } from "effect"
|
||||||
import * as Stream from "effect/Stream"
|
import * as Stream from "effect/Stream"
|
||||||
|
|||||||
@@ -110,9 +110,7 @@ const messages = (input: readonly ModelMessage[]) => {
|
|||||||
Message.make({
|
Message.make({
|
||||||
role: message.role,
|
role: message.role,
|
||||||
content: content(message.content),
|
content: content(message.content),
|
||||||
// Message provider options are already provider-native wire metadata
|
native: isRecord(message.providerOptions) ? { providerOptions: message.providerOptions } : undefined,
|
||||||
// (for example DeepSeek's reasoning_content continuation field).
|
|
||||||
native: isRecord(message.providerOptions) ? message.providerOptions : undefined,
|
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -70,6 +70,14 @@ export function stream(input: StreamInput): StreamResult {
|
|||||||
|
|
||||||
// Integration point with @opencode-ai/llm: native-request lowers session data
|
// Integration point with @opencode-ai/llm: native-request lowers session data
|
||||||
// into an LLMRequest, then LLMClient handles route selection and transport.
|
// into an LLMRequest, then LLMClient handles route selection and transport.
|
||||||
|
//
|
||||||
|
// ProviderTransform.providerOptions builds AI-SDK-shaped options for the
|
||||||
|
// selected SDK key (e.g. "openai") and the native LLM SDK reads the same
|
||||||
|
// keys via OpenAIOptions.* (store, reasoningEffort, reasoningSummary,
|
||||||
|
// include, textVerbosity, promptCacheKey). Both sides intentionally use
|
||||||
|
// OpenAI's official wire field names, so this is identity, not translation
|
||||||
|
// — if a field ever needs to differ between the two surfaces, the
|
||||||
|
// translation belongs here, not split across both packages.
|
||||||
const stream = input.llmClient.stream({
|
const stream = input.llmClient.stream({
|
||||||
request: LLMNative.request({
|
request: LLMNative.request({
|
||||||
model: input.model,
|
model: input.model,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Slug } from "@opencode-ai/core/util/slug"
|
import { Slug } from "@opencode-ai/core/util/slug"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { BackgroundJob } from "@/background/job"
|
import { BackgroundJob } from "@/background/job"
|
||||||
import { BusEvent } from "@/bus/bus-event"
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type * as SDK from "@opencode-ai/sdk/v2"
|
import type * as SDK from "@opencode-ai/sdk/v2"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import { Effect, Exit, Layer, Option, Schema, Scope, Context, Stream } from "effect"
|
import { Effect, Exit, Layer, Option, Schema, Scope, Context, Stream } from "effect"
|
||||||
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||||
import { Account } from "@/account/account"
|
import { Account } from "@/account/account"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { EventID } from "./schema"
|
|||||||
import { Context, Effect, Layer, Schema as EffectSchema } from "effect"
|
import { Context, Effect, Layer, Schema as EffectSchema } from "effect"
|
||||||
import type { DeepMutable } from "@opencode-ai/core/schema"
|
import type { DeepMutable } from "@opencode-ai/core/schema"
|
||||||
import { EventV2 } from "@opencode-ai/core/event"
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
import { serviceUse } from "@/effect/service-use"
|
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||||
import { InstanceState } from "@/effect/instance-state"
|
import { InstanceState } from "@/effect/instance-state"
|
||||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
import { EffectBridge } from "@/effect/bridge"
|
import { EffectBridge } from "@/effect/bridge"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -62,7 +62,7 @@ describe("RuntimeFlags", () => {
|
|||||||
expect(flags.experimentalEventSystem).toBe(true)
|
expect(flags.experimentalEventSystem).toBe(true)
|
||||||
expect(flags.experimentalWorkspaces).toBe(true)
|
expect(flags.experimentalWorkspaces).toBe(true)
|
||||||
expect(flags.experimentalIconDiscovery).toBe(true)
|
expect(flags.experimentalIconDiscovery).toBe(true)
|
||||||
expect(flags.experimentalNativeLlm).toBe(true)
|
expect(flags.experimentalNativeLlm).toBe(false)
|
||||||
expect(flags.client).toBe("desktop")
|
expect(flags.client).toBe("desktop")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -81,13 +81,13 @@ describe("RuntimeFlags", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("enables native LLM via dedicated or umbrella flag", () =>
|
it.effect("enables native LLM via dedicated flag only", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const explicit = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_EXPERIMENTAL_NATIVE_LLM: "true" })))
|
const explicit = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_EXPERIMENTAL_NATIVE_LLM: "true" })))
|
||||||
const umbrella = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_EXPERIMENTAL: "true" })))
|
const umbrella = yield* readFlags.pipe(Effect.provide(fromConfig({ OPENCODE_EXPERIMENTAL: "true" })))
|
||||||
|
|
||||||
expect(explicit.experimentalNativeLlm).toBe(true)
|
expect(explicit.experimentalNativeLlm).toBe(true)
|
||||||
expect(umbrella.experimentalNativeLlm).toBe(true)
|
expect(umbrella.experimentalNativeLlm).toBe(false)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+5
-5
File diff suppressed because one or more lines are too long
+4
-4
File diff suppressed because one or more lines are too long
@@ -7,15 +7,6 @@ import path from "path"
|
|||||||
import { tmpdirScoped } from "../fixture/fixture"
|
import { tmpdirScoped } from "../fixture/fixture"
|
||||||
import { GlobalBus } from "../../src/bus/global"
|
import { GlobalBus } from "../../src/bus/global"
|
||||||
import { ProjectID } from "../../src/project/schema"
|
import { ProjectID } from "../../src/project/schema"
|
||||||
import { Database } from "@/storage/db"
|
|
||||||
import { ProjectTable } from "@/project/project.sql"
|
|
||||||
import { SessionTable } from "@/session/session.sql"
|
|
||||||
import { PermissionTable } from "@/session/session.sql"
|
|
||||||
import { WorkspaceTable } from "@/control-plane/workspace.sql"
|
|
||||||
import { eq } from "drizzle-orm"
|
|
||||||
import { Hash } from "@opencode-ai/core/util/hash"
|
|
||||||
import { SessionID } from "@/session/schema"
|
|
||||||
import { WorkspaceID } from "@/control-plane/schema"
|
|
||||||
import { Cause, Effect, Exit, Layer, Stream } from "effect"
|
import { Cause, Effect, Exit, Layer, Stream } from "effect"
|
||||||
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
||||||
import { NodePath } from "@effect/platform-node"
|
import { NodePath } from "@effect/platform-node"
|
||||||
@@ -40,10 +31,6 @@ function run<A, E>(fn: (svc: Project.Interface) => Effect.Effect<A, E>) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function remoteProjectID(remote: string) {
|
|
||||||
return ProjectID.make(Hash.fast(`git-remote:${remote}`))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a mock ChildProcessSpawner layer that intercepts git subcommands
|
* Creates a mock ChildProcessSpawner layer that intercepts git subcommands
|
||||||
* matching `failArg` and returns exit code 128, while delegating everything
|
* matching `failArg` and returns exit code 128, while delegating everything
|
||||||
@@ -167,91 +154,28 @@ describe("Project.fromDirectory", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.live("prefers normalized origin remote over root commit", () =>
|
it.live("keeps root commit identity when origin exists", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const tmp = yield* tmpdirScoped({ git: true })
|
const tmp = yield* tmpdirScoped({ git: true })
|
||||||
yield* Effect.promise(() => $`git remote add origin git@github.com:Test-Org/Test-Repo.git`.cwd(tmp).quiet())
|
yield* Effect.promise(() => $`git remote add origin git@github.com:Test-Org/Test-Repo.git`.cwd(tmp).quiet())
|
||||||
|
|
||||||
const { project } = yield* run((svc) => svc.fromDirectory(tmp))
|
const { project } = yield* run((svc) => svc.fromDirectory(tmp))
|
||||||
|
const root = (yield* Effect.promise(() => $`git rev-list --max-parents=0 HEAD`.cwd(tmp).text())).trim()
|
||||||
|
|
||||||
expect(project.id).toBe(remoteProjectID("github.com/Test-Org/Test-Repo"))
|
expect(project.id).toBe(ProjectID.make(root))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.live("normalizes equivalent origin URL forms to the same project ID", () =>
|
it.live("keeps cached project identity when origin becomes available", () =>
|
||||||
Effect.gen(function* () {
|
|
||||||
const ssh = yield* tmpdirScoped({ git: true })
|
|
||||||
const https = yield* tmpdirScoped({ git: true })
|
|
||||||
yield* Effect.promise(() => $`git remote add origin git@github.com:owner/repo.git`.cwd(ssh).quiet())
|
|
||||||
yield* Effect.promise(() => $`git remote add origin https://github.com/owner/repo.git`.cwd(https).quiet())
|
|
||||||
|
|
||||||
const { project: a } = yield* run((svc) => svc.fromDirectory(ssh))
|
|
||||||
const { project: b } = yield* run((svc) => svc.fromDirectory(https))
|
|
||||||
|
|
||||||
expect(a.id).toBe(remoteProjectID("github.com/owner/repo"))
|
|
||||||
expect(b.id).toBe(a.id)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.live("migrates cached root project data when origin becomes available", () =>
|
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const tmp = yield* tmpdirScoped({ git: true })
|
const tmp = yield* tmpdirScoped({ git: true })
|
||||||
const projects = yield* Project.Service
|
const projects = yield* Project.Service
|
||||||
const { project: rootProject } = yield* projects.fromDirectory(tmp)
|
const { project: rootProject } = yield* projects.fromDirectory(tmp)
|
||||||
const remoteID = remoteProjectID("github.com/acme/app")
|
|
||||||
const sessionID = crypto.randomUUID() as SessionID
|
|
||||||
const workspaceID = WorkspaceID.ascending()
|
|
||||||
|
|
||||||
yield* Effect.sync(() => {
|
|
||||||
Database.use((db) => {
|
|
||||||
db.insert(SessionTable)
|
|
||||||
.values({
|
|
||||||
id: sessionID,
|
|
||||||
project_id: rootProject.id,
|
|
||||||
slug: sessionID,
|
|
||||||
directory: tmp,
|
|
||||||
title: "test",
|
|
||||||
version: "0.0.0-test",
|
|
||||||
time_created: Date.now(),
|
|
||||||
time_updated: Date.now(),
|
|
||||||
})
|
|
||||||
.run()
|
|
||||||
db.insert(PermissionTable)
|
|
||||||
.values({
|
|
||||||
project_id: rootProject.id,
|
|
||||||
data: [{ permission: "edit", pattern: "*", action: "allow" }],
|
|
||||||
time_created: Date.now(),
|
|
||||||
time_updated: Date.now(),
|
|
||||||
})
|
|
||||||
.run()
|
|
||||||
db.insert(WorkspaceTable)
|
|
||||||
.values({
|
|
||||||
id: workspaceID,
|
|
||||||
type: "local",
|
|
||||||
name: "test",
|
|
||||||
project_id: rootProject.id,
|
|
||||||
})
|
|
||||||
.run()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
yield* Effect.promise(() => $`git remote add origin git@github.com:acme/app.git`.cwd(tmp).quiet())
|
yield* Effect.promise(() => $`git remote add origin git@github.com:acme/app.git`.cwd(tmp).quiet())
|
||||||
|
|
||||||
const { project } = yield* projects.fromDirectory(tmp)
|
const { project } = yield* projects.fromDirectory(tmp)
|
||||||
|
|
||||||
expect(project.id).toBe(remoteID)
|
expect(project.id).toBe(rootProject.id)
|
||||||
expect(
|
|
||||||
Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, rootProject.id)).get()),
|
|
||||||
).toBeUndefined()
|
|
||||||
expect(
|
|
||||||
Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, sessionID)).get())?.project_id,
|
|
||||||
).toBe(remoteID)
|
|
||||||
expect(
|
|
||||||
Database.use((db) => db.select().from(PermissionTable).where(eq(PermissionTable.project_id, remoteID)).get()),
|
|
||||||
).toBeDefined()
|
|
||||||
expect(
|
|
||||||
Database.use((db) => db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, workspaceID)).get())
|
|
||||||
?.project_id,
|
|
||||||
).toBe(remoteID)
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -271,6 +271,7 @@ describe("ProviderTransform.options - gpt-5 textVerbosity", () => {
|
|||||||
const model = createGpt5Model("gpt-5.2")
|
const model = createGpt5Model("gpt-5.2")
|
||||||
const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
|
const result = ProviderTransform.options({ model, sessionID, providerOptions: {} })
|
||||||
expect(result.textVerbosity).toBe("low")
|
expect(result.textVerbosity).toBe("low")
|
||||||
|
expect(result.include).toEqual(["reasoning.encrypted_content"])
|
||||||
})
|
})
|
||||||
|
|
||||||
test("gpt-5.1 should have textVerbosity set to low", () => {
|
test("gpt-5.1 should have textVerbosity set to low", () => {
|
||||||
|
|||||||
@@ -336,10 +336,25 @@ const weatherTool = tool({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const toolRoundtrip = (
|
const toolRoundtrip = (
|
||||||
|
events: ReadonlyArray<LLMEvent>,
|
||||||
call: { readonly id: string; readonly name: string; readonly input: unknown },
|
call: { readonly id: string; readonly name: string; readonly input: unknown },
|
||||||
result: JSONValue,
|
result: JSONValue,
|
||||||
): ModelMessage[] => [
|
): ModelMessage[] => [
|
||||||
{ role: "assistant", content: [{ type: "tool-call", toolCallId: call.id, toolName: call.name, input: call.input }] },
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: [
|
||||||
|
...events.filter(LLMEvent.is.reasoningEnd).map((part) => ({
|
||||||
|
type: "reasoning" as const,
|
||||||
|
text: events
|
||||||
|
.filter(LLMEvent.is.reasoningDelta)
|
||||||
|
.filter((event) => event.id === part.id)
|
||||||
|
.map((event) => event.text)
|
||||||
|
.join(""),
|
||||||
|
providerMetadata: part.providerMetadata,
|
||||||
|
})),
|
||||||
|
{ type: "tool-call", toolCallId: call.id, toolName: call.name, input: call.input },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
role: "tool",
|
role: "tool",
|
||||||
content: [
|
content: [
|
||||||
@@ -395,7 +410,7 @@ const driveToolLoop = (scenario: RecordedScenario) =>
|
|||||||
|
|
||||||
const turn2 = yield* collect({
|
const turn2 = yield* collect({
|
||||||
...base,
|
...base,
|
||||||
messages: [userMessage, ...toolRoundtrip(toolCall!, WEATHER_RESULT)],
|
messages: [userMessage, ...toolRoundtrip(turn1, toolCall!, WEATHER_RESULT)],
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(LLMResponse.text({ events: turn2 })).toMatch(/Paris is sunny/i)
|
expect(LLMResponse.text({ events: turn2 })).toMatch(/Paris is sunny/i)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { Effect, Layer, Stream } from "effect"
|
|||||||
import { LLMNative } from "@/session/llm/native-request"
|
import { LLMNative } from "@/session/llm/native-request"
|
||||||
import { LLMNativeRuntime } from "@/session/llm/native-runtime"
|
import { LLMNativeRuntime } from "@/session/llm/native-runtime"
|
||||||
import type { Provider } from "@/provider/provider"
|
import type { Provider } from "@/provider/provider"
|
||||||
import { ProviderTransform } from "@/provider/transform"
|
|
||||||
import { ModelID, ProviderID } from "@/provider/schema"
|
import { ModelID, ProviderID } from "@/provider/schema"
|
||||||
import { OAUTH_DUMMY_KEY } from "@/auth"
|
import { OAUTH_DUMMY_KEY } from "@/auth"
|
||||||
import { testEffect } from "../lib/effect"
|
import { testEffect } from "../lib/effect"
|
||||||
@@ -71,21 +70,6 @@ const providerInfo: Provider.Info = {
|
|||||||
models: {},
|
models: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
const compatibleModel: Provider.Model = {
|
|
||||||
...baseModel,
|
|
||||||
id: ModelID.make("deepseek-v4-flash-free"),
|
|
||||||
providerID: ProviderID.make("opencode"),
|
|
||||||
api: {
|
|
||||||
id: "deepseek-v4-flash-free",
|
|
||||||
url: "https://ai.example.test/v1",
|
|
||||||
npm: "@ai-sdk/openai-compatible",
|
|
||||||
},
|
|
||||||
capabilities: {
|
|
||||||
...baseModel.capabilities,
|
|
||||||
interleaved: { field: "reasoning_content" },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const it = testEffect(
|
const it = testEffect(
|
||||||
LLMClient.layer.pipe(Layer.provide(Layer.mergeAll(RequestExecutor.defaultLayer, WebSocketExecutor.layer))),
|
LLMClient.layer.pipe(Layer.provide(Layer.mergeAll(RequestExecutor.defaultLayer, WebSocketExecutor.layer))),
|
||||||
)
|
)
|
||||||
@@ -342,70 +326,6 @@ describe("session.llm-native.request", () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it.effect("preserves OpenAI-compatible reasoning continuation and provider options", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const messages = ProviderTransform.message(
|
|
||||||
[
|
|
||||||
{ role: "user", content: "Audit the site" },
|
|
||||||
{
|
|
||||||
role: "assistant",
|
|
||||||
content: [
|
|
||||||
{ type: "reasoning", text: "I should inspect the page." },
|
|
||||||
{
|
|
||||||
type: "tool-call",
|
|
||||||
toolCallId: "call-1",
|
|
||||||
toolName: "devtools_new_page",
|
|
||||||
input: { url: "https://example.test" },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
] as ModelMessage[],
|
|
||||||
compatibleModel,
|
|
||||||
{},
|
|
||||||
)
|
|
||||||
|
|
||||||
const prepared = yield* LLMClient.prepare(
|
|
||||||
LLMNative.request({
|
|
||||||
model: compatibleModel,
|
|
||||||
apiKey: "test-key",
|
|
||||||
messages,
|
|
||||||
providerOptions: ProviderTransform.providerOptions(compatibleModel, {
|
|
||||||
reasoningEffort: "max",
|
|
||||||
textVerbosity: "low",
|
|
||||||
promptCacheKey: "session-1",
|
|
||||||
enable_thinking: true,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(prepared.body).toMatchObject({
|
|
||||||
model: "deepseek-v4-flash-free",
|
|
||||||
reasoning_effort: "max",
|
|
||||||
verbosity: "low",
|
|
||||||
prompt_cache_key: "session-1",
|
|
||||||
enable_thinking: true,
|
|
||||||
messages: [
|
|
||||||
{ role: "user", content: "Audit the site" },
|
|
||||||
{
|
|
||||||
role: "assistant",
|
|
||||||
content: null,
|
|
||||||
reasoning_content: "I should inspect the page.",
|
|
||||||
tool_calls: [
|
|
||||||
{
|
|
||||||
id: "call-1",
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "devtools_new_page",
|
|
||||||
arguments: '{"url":"https://example.test"}',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
test("selects native request routes for provider packages", () => {
|
test("selects native request routes for provider packages", () => {
|
||||||
const openai = LLMNative.model({
|
const openai = LLMNative.model({
|
||||||
model: { ...baseModel, api: { ...baseModel.api, url: "", npm: "@ai-sdk/openai" } },
|
model: { ...baseModel, api: { ...baseModel.api, url: "", npm: "@ai-sdk/openai" } },
|
||||||
@@ -671,7 +591,7 @@ describe("session.llm-native.request", () => {
|
|||||||
]),
|
]),
|
||||||
storedSession.user("Summarize it."),
|
storedSession.user("Summarize it."),
|
||||||
],
|
],
|
||||||
providerOptions: { openai: { store: false, includeEncryptedReasoning: true } },
|
providerOptions: { openai: { store: false, include: ["reasoning.encrypted_content"] } },
|
||||||
expectedBody: {
|
expectedBody: {
|
||||||
input: [
|
input: [
|
||||||
openAIResponses.user("What changed?"),
|
openAIResponses.user("What changed?"),
|
||||||
@@ -688,6 +608,45 @@ describe("session.llm-native.request", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("preserves empty encrypted OpenAI reasoning items before tool output", () =>
|
||||||
|
expectOpenAIResponsesRequest({
|
||||||
|
history: [
|
||||||
|
storedSession.assistant([
|
||||||
|
storedSession.openaiReasoning("", {
|
||||||
|
storedAs: "providerMetadata",
|
||||||
|
itemId: "rs_1",
|
||||||
|
encryptedContent: "encrypted-state",
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
providerOptions: { openai: { store: false, include: ["reasoning.encrypted_content"] } },
|
||||||
|
expectedBody: {
|
||||||
|
input: [{ type: "reasoning", id: "rs_1", summary: [], encrypted_content: "encrypted-state" }],
|
||||||
|
include: ["reasoning.encrypted_content"],
|
||||||
|
store: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("references stored OpenAI reasoning items by id", () =>
|
||||||
|
expectOpenAIResponsesRequest({
|
||||||
|
history: [
|
||||||
|
storedSession.assistant([
|
||||||
|
storedSession.openaiReasoning("Checked the previous diff.", {
|
||||||
|
storedAs: "providerMetadata",
|
||||||
|
itemId: "rs_1",
|
||||||
|
encryptedContent: null,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
providerOptions: { openai: { store: true } },
|
||||||
|
expectedBody: {
|
||||||
|
input: [{ type: "item_reference", id: "rs_1" }],
|
||||||
|
store: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("uses provider fetch override for native OpenAI OAuth requests", () =>
|
it.effect("uses provider fetch override for native OpenAI OAuth requests", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const captures: Array<{ url: string; body: unknown }> = []
|
const captures: Array<{ url: string; body: unknown }> = []
|
||||||
|
|||||||
@@ -1166,6 +1166,7 @@ describe("session.llm.stream", () => {
|
|||||||
expect(capture.body.model).toBe(model.id)
|
expect(capture.body.model).toBe(model.id)
|
||||||
expect(capture.body.stream).toBe(true)
|
expect(capture.body.stream).toBe(true)
|
||||||
expect((capture.body.reasoning as { effort?: string } | undefined)?.effort).toBe("high")
|
expect((capture.body.reasoning as { effort?: string } | undefined)?.effort).toBe("high")
|
||||||
|
expect(capture.body.include).toEqual(["reasoning.encrypted_content"])
|
||||||
expect(JSON.stringify(capture.body.input)).toContain("You are a helpful assistant.")
|
expect(JSON.stringify(capture.body.input)).toContain("You are a helpful assistant.")
|
||||||
expect(capture.body.input).toContainEqual({ role: "user", content: [{ type: "input_text", text: "Hello" }] })
|
expect(capture.body.input).toContainEqual({ role: "user", content: [{ type: "input_text", text: "Hello" }] })
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -595,8 +595,8 @@ opencode upgrade v0.1.48
|
|||||||
تقوم متغيرات البيئة التالية بتفعيل ميزات تجريبية قد تتغير أو تتم إزالتها.
|
تقوم متغيرات البيئة التالية بتفعيل ميزات تجريبية قد تتغير أو تتم إزالتها.
|
||||||
|
|
||||||
| المتغير | النوع | الوصف |
|
| المتغير | النوع | الوصف |
|
||||||
| ----------------------------------------------- | ------- | ------------------------------------------- |
|
| ----------------------------------------------- | ------- | -------------------------------------------- |
|
||||||
| `OPENCODE_EXPERIMENTAL` | boolean | تفعيل جميع الميزات التجريبية |
|
| `OPENCODE_EXPERIMENTAL` | boolean | تفعيل الميزات التجريبية الخاضعة للعلم الشامل |
|
||||||
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | تفعيل اكتشاف الأيقونات |
|
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | تفعيل اكتشاف الأيقونات |
|
||||||
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | تعطيل النسخ عند التحديد في TUI |
|
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | تعطيل النسخ عند التحديد في TUI |
|
||||||
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | المهلة الافتراضية لأوامر bash بالميلي ثانية |
|
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | المهلة الافتراضية لأوامر bash بالميلي ثانية |
|
||||||
@@ -608,3 +608,9 @@ opencode upgrade v0.1.48
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | تفعيل ميزات Exa التجريبية |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | تفعيل ميزات Exa التجريبية |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | تمكين TY LSP لملفات python |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | تمكين TY LSP لملفات python |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | تفعيل وضع الخطة |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | تفعيل وضع الخطة |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | تفعيل مهام subagent في الخلفية |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | تفعيل نظام الأحداث التجريبي |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | تفعيل مسار طلبات LLM الأصلي |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | تفعيل تنفيذ بحث الويب بالتوازي |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_SCOUT` | boolean | تفعيل Scout subagent |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_WORKSPACES` | boolean | تفعيل دعم مساحات العمل |
|
||||||
|
|||||||
@@ -593,8 +593,8 @@ OpenCode se može konfigurirati pomoću varijabli okruženja.
|
|||||||
Ove varijable okruženja omogućavaju eksperimentalne karakteristike koje se mogu promijeniti ili ukloniti.
|
Ove varijable okruženja omogućavaju eksperimentalne karakteristike koje se mogu promijeniti ili ukloniti.
|
||||||
|
|
||||||
| Varijabla | Tip | Opis |
|
| Varijabla | Tip | Opis |
|
||||||
| ----------------------------------------------- | ------- | ------------------------------------------------- |
|
| ----------------------------------------------- | ------- | ------------------------------------------------------- |
|
||||||
| `OPENCODE_EXPERIMENTAL` | boolean | Omogući sve eksperimentalne funkcije |
|
| `OPENCODE_EXPERIMENTAL` | boolean | Omogući eksperimentalne funkcije pod zbirnom zastavicom |
|
||||||
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | Omogući otkrivanje ikona |
|
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | Omogući otkrivanje ikona |
|
||||||
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | Onemogući kopiranje pri odabiru u TUI |
|
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | Onemogući kopiranje pri odabiru u TUI |
|
||||||
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | Zadano vremensko ograničenje za bash naredbe u ms |
|
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | Zadano vremensko ograničenje za bash naredbe u ms |
|
||||||
@@ -606,3 +606,9 @@ Ove varijable okruženja omogućavaju eksperimentalne karakteristike koje se mog
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Omogući eksperimentalne Exa funkcije |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Omogući eksperimentalne Exa funkcije |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Omogući TY LSP za python datoteke |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Omogući TY LSP za python datoteke |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Omogući Plan mod |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Omogući Plan mod |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | Omogući pozadinske zadatke subagenata |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Omogući eksperimentalni sistem događaja |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Omogući nativnu putanju LLM zahtjeva |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Omogući paralelno izvršavanje web pretrage |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_SCOUT` | boolean | Omogući Scout subagenta |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_WORKSPACES` | boolean | Omogući podršku za radne prostore |
|
||||||
|
|||||||
@@ -712,7 +712,7 @@ These environment variables enable experimental features that may change or be r
|
|||||||
|
|
||||||
| Variable | Type | Description |
|
| Variable | Type | Description |
|
||||||
| ----------------------------------------------- | ------- | --------------------------------------- |
|
| ----------------------------------------------- | ------- | --------------------------------------- |
|
||||||
| `OPENCODE_EXPERIMENTAL` | boolean | Enable all experimental features |
|
| `OPENCODE_EXPERIMENTAL` | boolean | Enable the experimental umbrella flag |
|
||||||
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | Enable icon discovery |
|
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | Enable icon discovery |
|
||||||
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | Disable copy on select in TUI |
|
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | Disable copy on select in TUI |
|
||||||
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | Default timeout for bash commands in ms |
|
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | Default timeout for bash commands in ms |
|
||||||
@@ -724,3 +724,9 @@ These environment variables enable experimental features that may change or be r
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Enable experimental Exa features |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Enable experimental Exa features |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Enable TY LSP for python files |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Enable TY LSP for python files |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Enable plan mode |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Enable plan mode |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | Enable background subagent tasks |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Enable experimental event system |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Enable native LLM request path |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Enable parallel web search execution |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_SCOUT` | boolean | Enable Scout subagent |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_WORKSPACES` | boolean | Enable workspace support |
|
||||||
|
|||||||
@@ -596,8 +596,8 @@ OpenCode kan konfigureres ved hjælp af miljøvariabler.
|
|||||||
Disse miljøvariabler muliggør eksperimentelle funktioner, der kan ændres eller fjernes.
|
Disse miljøvariabler muliggør eksperimentelle funktioner, der kan ændres eller fjernes.
|
||||||
|
|
||||||
| Variabel | Skriv | Beskrivelse |
|
| Variabel | Skriv | Beskrivelse |
|
||||||
| ----------------------------------------------- | ------- | ----------------------------------------- |
|
| ----------------------------------------------- | ------- | ---------------------------------------------------- |
|
||||||
| `OPENCODE_EXPERIMENTAL` | boolean | Aktiver alle eksperimentelle funktioner |
|
| `OPENCODE_EXPERIMENTAL` | boolean | Aktiver eksperimentelle funktioner under samleflaget |
|
||||||
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | Aktiver ikonopdagelse |
|
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | Aktiver ikonopdagelse |
|
||||||
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | Deaktiver kopi ved valg i TUI |
|
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | Deaktiver kopi ved valg i TUI |
|
||||||
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | nummer | Standard timeout for bash-kommandoer i ms |
|
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | nummer | Standard timeout for bash-kommandoer i ms |
|
||||||
@@ -609,3 +609,9 @@ Disse miljøvariabler muliggør eksperimentelle funktioner, der kan ændres elle
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Aktive eksperimenter Exa-funktioner |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Aktive eksperimenter Exa-funktioner |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Aktiver TY LSP for python-filer |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Aktiver TY LSP for python-filer |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Aktiver plantilstand |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Aktiver plantilstand |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | Aktiver baggrundsopgaver for subagenter |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Aktiver eksperimentelt hændelsessystem |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Aktiver native LLM-anmodningssti |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Aktiver parallel udførelse af websøgning |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_SCOUT` | boolean | Aktiver Scout-subagent |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_WORKSPACES` | boolean | Aktiver workspace-understøttelse |
|
||||||
|
|||||||
@@ -595,8 +595,8 @@ OpenCode kann mithilfe von Umgebungsvariablen konfiguriert werden.
|
|||||||
Diese Umgebungsvariablen ermöglichen experimentelle Funktionen, die sich ändern oder entfernt werden können.
|
Diese Umgebungsvariablen ermöglichen experimentelle Funktionen, die sich ändern oder entfernt werden können.
|
||||||
|
|
||||||
| Variable | Typ | Beschreibung |
|
| Variable | Typ | Beschreibung |
|
||||||
| ----------------------------------------------- | --------------- | ------------------------------------------------------- |
|
| ----------------------------------------------- | --------------- | --------------------------------------------------------- |
|
||||||
| `OPENCODE_EXPERIMENTAL` | boolescher Wert | Alle experimentellen Funktionen aktivieren |
|
| `OPENCODE_EXPERIMENTAL` | boolescher Wert | Experimentelle Funktionen unter dem Sammelflag aktivieren |
|
||||||
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolescher Wert | Symbolerkennung aktivieren |
|
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolescher Wert | Symbolerkennung aktivieren |
|
||||||
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolescher Wert | Kopieren bei Auswahl in TUI deaktivieren |
|
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolescher Wert | Kopieren bei Auswahl in TUI deaktivieren |
|
||||||
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | Zahl | Standard-Timeout für Bash-Befehle in ms |
|
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | Zahl | Standard-Timeout für Bash-Befehle in ms |
|
||||||
@@ -608,3 +608,9 @@ Diese Umgebungsvariablen ermöglichen experimentelle Funktionen, die sich änder
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolescher Wert | Experimentelle Exa-Funktionen aktivieren |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolescher Wert | Experimentelle Exa-Funktionen aktivieren |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolescher Wert | TY LSP für Python-Dateien aktivieren |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolescher Wert | TY LSP für Python-Dateien aktivieren |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolescher Wert | Planmodus aktivieren |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolescher Wert | Planmodus aktivieren |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolescher Wert | Hintergrundaufgaben für Subagenten aktivieren |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolescher Wert | Experimentelles Ereignissystem aktivieren |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolescher Wert | Nativen LLM-Anfragepfad aktivieren |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolescher Wert | Parallele Websuche aktivieren |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_SCOUT` | boolescher Wert | Scout-Subagent aktivieren |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_WORKSPACES` | boolescher Wert | Workspace-Unterstützung aktivieren |
|
||||||
|
|||||||
@@ -596,7 +596,7 @@ Estas variables de entorno habilitan funciones experimentales que pueden cambiar
|
|||||||
|
|
||||||
| Variable | Type | Description |
|
| Variable | Type | Description |
|
||||||
| ----------------------------------------------- | -------- | ---------------------------------------------------------- |
|
| ----------------------------------------------- | -------- | ---------------------------------------------------------- |
|
||||||
| `OPENCODE_EXPERIMENTAL` | booleano | Habilitar todas las funciones experimentales |
|
| `OPENCODE_EXPERIMENTAL` | booleano | Habilitar funciones experimentales de la bandera global |
|
||||||
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | booleano | Habilitar descubrimiento de íconos |
|
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | booleano | Habilitar descubrimiento de íconos |
|
||||||
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | booleano | Deshabilitar copia al seleccionar en TUI |
|
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | booleano | Deshabilitar copia al seleccionar en TUI |
|
||||||
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | número | Tiempo de espera predeterminado para comandos bash en ms |
|
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | número | Tiempo de espera predeterminado para comandos bash en ms |
|
||||||
@@ -608,3 +608,9 @@ Estas variables de entorno habilitan funciones experimentales que pueden cambiar
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | booleano | Habilitar funciones experimentales de Exa |
|
| `OPENCODE_EXPERIMENTAL_EXA` | booleano | Habilitar funciones experimentales de Exa |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | booleano | Habilitar Habilitar TY LSP para archivos python |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | booleano | Habilitar Habilitar TY LSP para archivos python |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | booleano | Habilitar modo de plan |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | booleano | Habilitar modo de plan |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | booleano | Habilitar tareas de subagentes en segundo plano |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | booleano | Habilitar sistema de eventos experimental |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | booleano | Habilitar ruta nativa de solicitud LLM |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | booleano | Habilitar ejecución paralela de búsqueda web |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_SCOUT` | booleano | Habilitar subagente Scout |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_WORKSPACES` | booleano | Habilitar soporte de espacios de trabajo |
|
||||||
|
|||||||
@@ -597,7 +597,7 @@ Ces variables d'environnement activent des fonctionnalités expérimentales qui
|
|||||||
|
|
||||||
| Variables | Type | Description |
|
| Variables | Type | Description |
|
||||||
| ----------------------------------------------- | ------- | --------------------------------------------------------------- |
|
| ----------------------------------------------- | ------- | --------------------------------------------------------------- |
|
||||||
| `OPENCODE_EXPERIMENTAL` | booléen | Activer toutes les fonctionnalités expérimentales |
|
| `OPENCODE_EXPERIMENTAL` | booléen | Activer les fonctionnalités expérimentales du drapeau global |
|
||||||
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | booléen | Activer la découverte d'icônes |
|
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | booléen | Activer la découverte d'icônes |
|
||||||
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | booléen | Désactiver la copie lors de la sélection dans TUI |
|
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | booléen | Désactiver la copie lors de la sélection dans TUI |
|
||||||
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | numéro | Délai d'expiration par défaut pour les commandes bash en ms |
|
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | numéro | Délai d'expiration par défaut pour les commandes bash en ms |
|
||||||
@@ -609,3 +609,9 @@ Ces variables d'environnement activent des fonctionnalités expérimentales qui
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | booléen | Activer les fonctionnalités Exa expérimentales |
|
| `OPENCODE_EXPERIMENTAL_EXA` | booléen | Activer les fonctionnalités Exa expérimentales |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | booléen | Activer TY LSP pour les fichiers python |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | booléen | Activer TY LSP pour les fichiers python |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | booléen | Activer le mode plan |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | booléen | Activer le mode plan |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | booléen | Activer les tâches de sous-agents en arrière-plan |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | booléen | Activer le système d'événements expérimental |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | booléen | Activer le chemin de requête LLM natif |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | booléen | Activer l'exécution parallèle de la recherche web |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_SCOUT` | booléen | Activer le sous-agent Scout |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_WORKSPACES` | booléen | Activer la prise en charge des espaces de travail |
|
||||||
|
|||||||
@@ -596,8 +596,8 @@ OpenCode può essere configurato tramite variabili d'ambiente.
|
|||||||
Queste variabili d'ambiente abilitano funzionalità sperimentali che potrebbero cambiare o essere rimosse.
|
Queste variabili d'ambiente abilitano funzionalità sperimentali che potrebbero cambiare o essere rimosse.
|
||||||
|
|
||||||
| Variabile | Tipo | Descrizione |
|
| Variabile | Tipo | Descrizione |
|
||||||
| ----------------------------------------------- | ------- | ------------------------------------------ |
|
| ----------------------------------------------- | ------- | ------------------------------------------------------- |
|
||||||
| `OPENCODE_EXPERIMENTAL` | boolean | Abilita tutte le funzionalità sperimentali |
|
| `OPENCODE_EXPERIMENTAL` | boolean | Abilita funzionalità sperimentali sotto il flag globale |
|
||||||
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | Abilita icon discovery |
|
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | Abilita icon discovery |
|
||||||
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | Disabilita copy on select nella TUI |
|
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | Disabilita copy on select nella TUI |
|
||||||
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | Timeout di default per comandi bash in ms |
|
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | Timeout di default per comandi bash in ms |
|
||||||
@@ -609,3 +609,9 @@ Queste variabili d'ambiente abilitano funzionalità sperimentali che potrebbero
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Abilita funzionalità Exa sperimentali |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Abilita funzionalità Exa sperimentali |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Abilita TY LSP per i file python |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Abilita TY LSP per i file python |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Abilita plan mode |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Abilita plan mode |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | Abilita task subagent in background |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Abilita sistema eventi sperimentale |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Abilita percorso nativo di richiesta LLM |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Abilita esecuzione parallela della ricerca web |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_SCOUT` | boolean | Abilita subagent Scout |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_WORKSPACES` | boolean | Abilita supporto workspace |
|
||||||
|
|||||||
@@ -596,7 +596,7 @@ OpenCode は環境変数を使用して構成できます。
|
|||||||
|
|
||||||
| 変数 | タイプ | 説明 |
|
| 変数 | タイプ | 説明 |
|
||||||
| ----------------------------------------------- | -------- | ------------------------------------------------ |
|
| ----------------------------------------------- | -------- | ------------------------------------------------ |
|
||||||
| `OPENCODE_EXPERIMENTAL` | ブール値 | すべての実験的機能を有効にする |
|
| `OPENCODE_EXPERIMENTAL` | ブール値 | アンブレラフラグ配下の実験的機能を有効にする |
|
||||||
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | ブール値 | アイコン検出を有効にする |
|
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | ブール値 | アイコン検出を有効にする |
|
||||||
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | ブール値 | TUI で選択時のコピーを無効にする |
|
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | ブール値 | TUI で選択時のコピーを無効にする |
|
||||||
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | 数値 | bash コマンドのデフォルトのタイムアウト (ミリ秒) |
|
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | 数値 | bash コマンドのデフォルトのタイムアウト (ミリ秒) |
|
||||||
@@ -608,3 +608,9 @@ OpenCode は環境変数を使用して構成できます。
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | ブール値 | 実験的な Exa 機能を有効にする |
|
| `OPENCODE_EXPERIMENTAL_EXA` | ブール値 | 実験的な Exa 機能を有効にする |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | ブール値 | python ファイルの TY LSP を有効にする |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | ブール値 | python ファイルの TY LSP を有効にする |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | ブール値 | プランモードを有効にする |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | ブール値 | プランモードを有効にする |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | ブール値 | バックグラウンド subagent タスクを有効にする |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | ブール値 | 実験的なイベントシステムを有効にする |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | ブール値 | ネイティブ LLM リクエスト経路を有効にする |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | ブール値 | 並列 Web 検索実行を有効にする |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_SCOUT` | ブール値 | Scout subagent を有効にする |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_WORKSPACES` | ブール値 | ワークスペースサポートを有効にする |
|
||||||
|
|||||||
@@ -595,8 +595,8 @@ OpenCode는 환경 변수로도 구성할 수 있습니다.
|
|||||||
아래 환경 변수는 변경되거나 제거될 수 있는 실험 기능을 활성화합니다.
|
아래 환경 변수는 변경되거나 제거될 수 있는 실험 기능을 활성화합니다.
|
||||||
|
|
||||||
| 변수 | 타입 | 설명 |
|
| 변수 | 타입 | 설명 |
|
||||||
| ----------------------------------------------- | ------- | -------------------------------- |
|
| ----------------------------------------------- | ------- | ------------------------------------------- |
|
||||||
| `OPENCODE_EXPERIMENTAL` | boolean | 모든 실험 기능 활성화 |
|
| `OPENCODE_EXPERIMENTAL` | boolean | 엄브렐라 플래그로 제어되는 실험 기능 활성화 |
|
||||||
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | 아이콘 탐색 활성화 |
|
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | 아이콘 탐색 활성화 |
|
||||||
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | TUI에서 선택 시 복사 비활성화 |
|
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | TUI에서 선택 시 복사 비활성화 |
|
||||||
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | bash 명령 기본 타임아웃(ms) |
|
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | bash 명령 기본 타임아웃(ms) |
|
||||||
@@ -608,3 +608,9 @@ OpenCode는 환경 변수로도 구성할 수 있습니다.
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | 실험적 Exa 기능 활성화 |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | 실험적 Exa 기능 활성화 |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | python 파일에 대해 TY LSP 활성화 |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | python 파일에 대해 TY LSP 활성화 |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Plan mode 활성화 |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Plan mode 활성화 |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | 백그라운드 subagent 작업 활성화 |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | 실험적 이벤트 시스템 활성화 |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | 네이티브 LLM 요청 경로 활성화 |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | 병렬 웹 검색 실행 활성화 |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_SCOUT` | boolean | Scout subagent 활성화 |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_WORKSPACES` | boolean | workspace 지원 활성화 |
|
||||||
|
|||||||
@@ -596,8 +596,8 @@ OpenCode kan konfigureres ved hjelp av miljøvariabler.
|
|||||||
Disse miljøvariablene muliggjør eksperimentelle funksjoner som kan endres eller fjernes.
|
Disse miljøvariablene muliggjør eksperimentelle funksjoner som kan endres eller fjernes.
|
||||||
|
|
||||||
| Variabel | Type | Beskrivelse |
|
| Variabel | Type | Beskrivelse |
|
||||||
| ----------------------------------------------- | ------ | --------------------------------------------- |
|
| ----------------------------------------------- | ------ | ----------------------------------------------------- |
|
||||||
| `OPENCODE_EXPERIMENTAL` | boolsk | Aktiver alle eksperimentelle funksjoner |
|
| `OPENCODE_EXPERIMENTAL` | boolsk | Aktiver eksperimentelle funksjoner under samleflagget |
|
||||||
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolsk | Aktiver ikonoppdagelse |
|
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolsk | Aktiver ikonoppdagelse |
|
||||||
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolsk | Deaktiver kopi ved valg i TUI |
|
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolsk | Deaktiver kopi ved valg i TUI |
|
||||||
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | tall | Standard tidsavbrudd for bash-kommandoer i ms |
|
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | tall | Standard tidsavbrudd for bash-kommandoer i ms |
|
||||||
@@ -609,3 +609,9 @@ Disse miljøvariablene muliggjør eksperimentelle funksjoner som kan endres elle
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolsk | Aktiver eksperimentelle Exa-funksjoner |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolsk | Aktiver eksperimentelle Exa-funksjoner |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolsk | Aktiver TY LSP for python-filer |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolsk | Aktiver TY LSP for python-filer |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolsk | Aktiver planmodus |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolsk | Aktiver planmodus |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolsk | Aktiver bakgrunnsoppgaver for subagenter |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolsk | Aktiver eksperimentelt hendelsessystem |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolsk | Aktiver innebygd LLM-forespørselsvei |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolsk | Aktiver parallell kjøring av websøk |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_SCOUT` | boolsk | Aktiver Scout-subagent |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_WORKSPACES` | boolsk | Aktiver arbeidsområde-støtte |
|
||||||
|
|||||||
@@ -596,8 +596,8 @@ OpenCode można skonfigurować za pomocą zmiennych środowiskowych.
|
|||||||
Te zmienne włączają funkcje eksperymentalne, które mogą ulec zmianie lub zostać usunięte.
|
Te zmienne włączają funkcje eksperymentalne, które mogą ulec zmianie lub zostać usunięte.
|
||||||
|
|
||||||
| Zmienna | Typ | Opis |
|
| Zmienna | Typ | Opis |
|
||||||
| ----------------------------------------------- | ------- | -------------------------------------------- |
|
| ----------------------------------------------- | ------- | --------------------------------------------------- |
|
||||||
| `OPENCODE_EXPERIMENTAL` | boolean | Włącz wszystkie funkcje eksperymentalne |
|
| `OPENCODE_EXPERIMENTAL` | boolean | Włącz funkcje eksperymentalne objęte flagą zbiorczą |
|
||||||
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | Włącz wykrywanie ikon |
|
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | Włącz wykrywanie ikon |
|
||||||
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | Wyłącz kopiowanie przy zaznaczaniu w TUI |
|
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | Wyłącz kopiowanie przy zaznaczaniu w TUI |
|
||||||
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | Domyślny limit czasu dla narzędzia bash w ms |
|
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | Domyślny limit czasu dla narzędzia bash w ms |
|
||||||
@@ -609,3 +609,9 @@ Te zmienne włączają funkcje eksperymentalne, które mogą ulec zmianie lub zo
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Włącz funkcje eksperymentalne Exa |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Włącz funkcje eksperymentalne Exa |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Włącz TY LSP dla plików python |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Włącz TY LSP dla plików python |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Włącz tryb planowania |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Włącz tryb planowania |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | Włącz zadania subagentów w tle |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Włącz eksperymentalny system zdarzeń |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Włącz natywną ścieżkę żądań LLM |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Włącz równoległe wykonywanie wyszukiwania web |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_SCOUT` | boolean | Włącz subagenta Scout |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_WORKSPACES` | boolean | Włącz obsługę obszarów roboczych |
|
||||||
|
|||||||
@@ -596,7 +596,7 @@ Essas variáveis de ambiente habilitam recursos experimentais que podem mudar ou
|
|||||||
|
|
||||||
| Variável | Tipo | Descrição |
|
| Variável | Tipo | Descrição |
|
||||||
| ----------------------------------------------- | ------- | --------------------------------------------------------- |
|
| ----------------------------------------------- | ------- | --------------------------------------------------------- |
|
||||||
| `OPENCODE_EXPERIMENTAL` | boolean | Habilitar todos os recursos experimentais |
|
| `OPENCODE_EXPERIMENTAL` | boolean | Habilitar recursos experimentais sob o sinalizador global |
|
||||||
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | Habilitar descoberta de ícones |
|
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | Habilitar descoberta de ícones |
|
||||||
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | Desabilitar cópia ao selecionar no TUI |
|
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | Desabilitar cópia ao selecionar no TUI |
|
||||||
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | Tempo limite padrão para comandos bash em ms |
|
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | Tempo limite padrão para comandos bash em ms |
|
||||||
@@ -608,3 +608,9 @@ Essas variáveis de ambiente habilitam recursos experimentais que podem mudar ou
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Habilitar recursos experimentais do Exa |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Habilitar recursos experimentais do Exa |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Habilitar TY LSP para arquivos python |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Habilitar TY LSP para arquivos python |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Habilitar modo de plano |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Habilitar modo de plano |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | Habilitar tarefas de subagentes em segundo plano |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Habilitar sistema de eventos experimental |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Habilitar caminho nativo de requisição LLM |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Habilitar execução paralela de busca web |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_SCOUT` | boolean | Habilitar subagente Scout |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_WORKSPACES` | boolean | Habilitar suporte a espaços de trabalho |
|
||||||
|
|||||||
@@ -597,7 +597,7 @@ opencode можно настроить с помощью переменных с
|
|||||||
|
|
||||||
| Переменная | Тип | Описание |
|
| Переменная | Тип | Описание |
|
||||||
| ----------------------------------------------- | ------------------- | ------------------------------------------------------ |
|
| ----------------------------------------------- | ------------------- | ------------------------------------------------------ |
|
||||||
| `OPENCODE_EXPERIMENTAL` | логическое значение | Включить все экспериментальные функции |
|
| `OPENCODE_EXPERIMENTAL` | логическое значение | Включить экспериментальные функции под общим флагом |
|
||||||
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | логическое значение | Включить обнаружение значков |
|
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | логическое значение | Включить обнаружение значков |
|
||||||
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | логическое значение | Отключить копирование при выборе в TUI |
|
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | логическое значение | Отключить копирование при выборе в TUI |
|
||||||
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | число | Таймаут по умолчанию для команд bash в мс |
|
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | число | Таймаут по умолчанию для команд bash в мс |
|
||||||
@@ -609,3 +609,9 @@ opencode можно настроить с помощью переменных с
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | логическое значение | Включить экспериментальные функции Exa |
|
| `OPENCODE_EXPERIMENTAL_EXA` | логическое значение | Включить экспериментальные функции Exa |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | логическое значение | Включить TY LSP для файлов python |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | логическое значение | Включить TY LSP для файлов python |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | логическое значение | Включить режим плана |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | логическое значение | Включить режим плана |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | логическое значение | Включить фоновые задачи субагентов |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | логическое значение | Включить экспериментальную систему событий |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | логическое значение | Включить нативный путь запросов LLM |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | логическое значение | Включить параллельное выполнение веб-поиска |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_SCOUT` | логическое значение | Включить субагент Scout |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_WORKSPACES` | логическое значение | Включить поддержку рабочих областей |
|
||||||
|
|||||||
@@ -598,7 +598,7 @@ OpenCode สามารถกำหนดค่าโดยใช้ตัว
|
|||||||
|
|
||||||
| ตัวแปร | ชนิด | คำอธิบาย |
|
| ตัวแปร | ชนิด | คำอธิบาย |
|
||||||
| ----------------------------------------------- | ------- | ---------------------------------------------- |
|
| ----------------------------------------------- | ------- | ---------------------------------------------- |
|
||||||
| `OPENCODE_EXPERIMENTAL` | Boolean | เปิดใช้งานฟีเจอร์ทดลองทั้งหมด |
|
| `OPENCODE_EXPERIMENTAL` | Boolean | เปิดใช้งานฟีเจอร์ทดลองภายใต้แฟล็กรวม |
|
||||||
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | Boolean | การค้นหาไอคอนทดลอง |
|
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | Boolean | การค้นหาไอคอนทดลอง |
|
||||||
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | Boolean | ปิดใช้งานการคัดลอกเมื่อเลือกใน TUI |
|
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | Boolean | ปิดใช้งานการคัดลอกเมื่อเลือกใน TUI |
|
||||||
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | Number | การหมดเวลาเริ่มต้นสำหรับคำสั่ง bash ในหน่วย ms |
|
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | Number | การหมดเวลาเริ่มต้นสำหรับคำสั่ง bash ในหน่วย ms |
|
||||||
@@ -610,3 +610,9 @@ OpenCode สามารถกำหนดค่าโดยใช้ตัว
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | Boolean | ฟีเจอร์ Exa ทดลอง |
|
| `OPENCODE_EXPERIMENTAL_EXA` | Boolean | ฟีเจอร์ Exa ทดลอง |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | Boolean | เปิดใช้งาน TY LSP สำหรับไฟล์ python |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | Boolean | เปิดใช้งาน TY LSP สำหรับไฟล์ python |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | Boolean | เปิดใช้งาน Plan mode |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | Boolean | เปิดใช้งาน Plan mode |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | Boolean | เปิดใช้งานงาน subagent เบื้องหลัง |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | Boolean | เปิดใช้งานระบบเหตุการณ์ทดลอง |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | Boolean | เปิดใช้งานเส้นทางคำขอ LLM แบบ native |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | Boolean | เปิดใช้งานการค้นหาเว็บแบบขนาน |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_SCOUT` | Boolean | เปิดใช้งาน Scout subagent |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_WORKSPACES` | Boolean | เปิดใช้งานการรองรับ workspace |
|
||||||
|
|||||||
@@ -597,7 +597,7 @@ Bu ortam değişkenleri değişebilecek veya kaldırılabilecek deneysel özelli
|
|||||||
|
|
||||||
| Değişken | Tip | Tanım |
|
| Değişken | Tip | Tanım |
|
||||||
| ----------------------------------------------- | ------- | ------------------------------------------------------- |
|
| ----------------------------------------------- | ------- | ------------------------------------------------------- |
|
||||||
| `OPENCODE_EXPERIMENTAL` | boolean | Tüm deneysel özellikleri etkinleştir |
|
| `OPENCODE_EXPERIMENTAL` | boolean | Şemsiye bayrağa bağlı deneysel özellikleri etkinleştir |
|
||||||
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | Simge bulmayı etkinleştir |
|
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | Simge bulmayı etkinleştir |
|
||||||
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | TUI'da seçim yapıldığında kopyalamayı devre dışı bırak |
|
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | TUI'da seçim yapıldığında kopyalamayı devre dışı bırak |
|
||||||
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | MS cinsinden bash komutları için varsayılan zaman aşımı |
|
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | MS cinsinden bash komutları için varsayılan zaman aşımı |
|
||||||
@@ -609,3 +609,9 @@ Bu ortam değişkenleri değişebilecek veya kaldırılabilecek deneysel özelli
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Deneysel Exa özelliklerini etkinleştirin |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Deneysel Exa özelliklerini etkinleştirin |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | python dosyaları için TY LSP'yi etkinleştir |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | python dosyaları için TY LSP'yi etkinleştir |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Plan modunu etkinleştir |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Plan modunu etkinleştir |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | Arka plan alt ajan görevlerini etkinleştir |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | Deneysel olay sistemini etkinleştir |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | Yerel LLM istek yolunu etkinleştir |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | Paralel web araması yürütmesini etkinleştir |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_SCOUT` | boolean | Scout alt ajanını etkinleştir |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_WORKSPACES` | boolean | Çalışma alanı desteğini etkinleştir |
|
||||||
|
|||||||
@@ -597,7 +597,7 @@ OpenCode 可以通过环境变量进行配置。
|
|||||||
|
|
||||||
| 变量 | 类型 | 描述 |
|
| 变量 | 类型 | 描述 |
|
||||||
| ----------------------------------------------- | ------- | ------------------------------- |
|
| ----------------------------------------------- | ------- | ------------------------------- |
|
||||||
| `OPENCODE_EXPERIMENTAL` | boolean | 启用所有实验性功能 |
|
| `OPENCODE_EXPERIMENTAL` | boolean | 启用受总开关控制的实验性功能 |
|
||||||
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | 启用图标发现 |
|
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | 启用图标发现 |
|
||||||
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | 禁用 TUI 中的选中即复制 |
|
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | 禁用 TUI 中的选中即复制 |
|
||||||
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | bash 命令的默认超时时间(毫秒) |
|
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | bash 命令的默认超时时间(毫秒) |
|
||||||
@@ -609,3 +609,9 @@ OpenCode 可以通过环境变量进行配置。
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | 启用实验性 Exa 功能 |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | 启用实验性 Exa 功能 |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | 为 python 文件启用 TY LSP |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | 为 python 文件启用 TY LSP |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | 启用计划模式 |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | 启用计划模式 |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | 启用后台子代理任务 |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | 启用实验性事件系统 |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | 启用原生 LLM 请求路径 |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | 启用并行 Web 搜索执行 |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_SCOUT` | boolean | 启用 Scout 子代理 |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_WORKSPACES` | boolean | 启用工作区支持 |
|
||||||
|
|||||||
@@ -597,7 +597,7 @@ OpenCode 可以透過環境變數進行設定。
|
|||||||
|
|
||||||
| 變數 | 類型 | 說明 |
|
| 變數 | 類型 | 說明 |
|
||||||
| ----------------------------------------------- | ------- | ------------------------------- |
|
| ----------------------------------------------- | ------- | ------------------------------- |
|
||||||
| `OPENCODE_EXPERIMENTAL` | boolean | 啟用所有實驗性功能 |
|
| `OPENCODE_EXPERIMENTAL` | boolean | 啟用受總開關控制的實驗性功能 |
|
||||||
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | 啟用圖示探索 |
|
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | boolean | 啟用圖示探索 |
|
||||||
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | 停用 TUI 中的選取即複製 |
|
| `OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT` | boolean | 停用 TUI 中的選取即複製 |
|
||||||
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | bash 指令的預設逾時時間(毫秒) |
|
| `OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS` | number | bash 指令的預設逾時時間(毫秒) |
|
||||||
@@ -609,3 +609,9 @@ OpenCode 可以透過環境變數進行設定。
|
|||||||
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | 啟用實驗性 Exa 功能 |
|
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | 啟用實驗性 Exa 功能 |
|
||||||
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | 為 python 檔案啟用 TY LSP |
|
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | 為 python 檔案啟用 TY LSP |
|
||||||
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | 啟用計畫模式 |
|
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | 啟用計畫模式 |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` | boolean | 啟用背景子代理任務 |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_EVENT_SYSTEM` | boolean | 啟用實驗性事件系統 |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_NATIVE_LLM` | boolean | 啟用原生 LLM 請求路徑 |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_PARALLEL` | boolean | 啟用平行 Web 搜尋執行 |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_SCOUT` | boolean | 啟用 Scout 子代理 |
|
||||||
|
| `OPENCODE_EXPERIMENTAL_WORKSPACES` | boolean | 啟用工作區支援 |
|
||||||
|
|||||||
Reference in New Issue
Block a user