diff --git a/.github/APPROVED_CONTRIBUTORS b/.github/APPROVED_CONTRIBUTORS
index 0513093f..7695e96f 100644
--- a/.github/APPROVED_CONTRIBUTORS
+++ b/.github/APPROVED_CONTRIBUTORS
@@ -237,3 +237,9 @@ davidlifschitz pr
vdxz pr
dangooddd pr
+
+Mearman pr
+
+dodiego pr
+
+any-victor pr
diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml
index cf50ffde..43cb73c5 100644
--- a/.github/ISSUE_TEMPLATE/bug.yml
+++ b/.github/ISSUE_TEMPLATE/bug.yml
@@ -11,6 +11,8 @@ body:
Keep this short. If it doesn't fit on one screen, it's too long. Write in your own voice.
+ **Important:** before reporting an issue in core, please validate first with `pi -ne` that this is not caused by an extension you loaded.
+
- type: textarea
id: description
attributes:
diff --git a/.github/ISSUE_TEMPLATE/package-report.yml b/.github/ISSUE_TEMPLATE/package-report.yml
new file mode 100644
index 00000000..846e25ee
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/package-report.yml
@@ -0,0 +1,49 @@
+name: Package Report
+description: Report a problematic Pi package listed on pi.dev
+labels: ["package-report"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Use this form to report a package listed on pi.dev. For Pi core bugs, use the bug report template instead.
+
+ New issues from new contributors are auto-closed by default. Maintainers review auto-closed issues daily. Issues that do not meet the quality bar in [CONTRIBUTING.md](https://github.com/earendil-works/pi-mono/blob/main/CONTRIBUTING.md) will not be reopened or receive a reply.
+
+ Keep this short. If it doesn't fit on one screen, it's too long. Write in your own voice.
+
+ - type: input
+ id: package-name
+ attributes:
+ label: Package name
+ description: The npm package name from pi.dev.
+ placeholder: "@scope/package"
+ validations:
+ required: true
+
+ - type: input
+ id: package-version
+ attributes:
+ label: Version
+ description: The package version shown on pi.dev.
+ placeholder: "0.1.0"
+ validations:
+ required: false
+
+ - type: dropdown
+ id: report-type
+ attributes:
+ label: What are you reporting?
+ options:
+ - Malicious or unsafe behavior
+ - Impersonation
+ - Trademark / TOS Violations
+ validations:
+ required: true
+
+ - type: textarea
+ id: details
+ attributes:
+ label: Details
+ description: Describe the concern and include links, logs, or screenshots if helpful.
+ validations:
+ required: true
diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml
index 4a081581..355ad1cd 100644
--- a/.github/workflows/build-binaries.yml
+++ b/.github/workflows/build-binaries.yml
@@ -59,28 +59,27 @@ jobs:
run: |
cd packages/coding-agent/binaries
+ release_assets=(
+ pi-darwin-arm64.tar.gz
+ pi-darwin-x64.tar.gz
+ pi-linux-x64.tar.gz
+ pi-linux-arm64.tar.gz
+ pi-windows-x64.zip
+ pi-windows-arm64.zip
+ )
+ sha256sum "${release_assets[@]}" > SHA256SUMS
+ release_assets+=(SHA256SUMS)
+
if gh release view "${RELEASE_TAG}" >/dev/null 2>&1; then
gh release edit "${RELEASE_TAG}" \
--title "${RELEASE_TAG}" \
--notes-file /tmp/release-notes.md
- gh release upload "${RELEASE_TAG}" \
- pi-darwin-arm64.tar.gz \
- pi-darwin-x64.tar.gz \
- pi-linux-x64.tar.gz \
- pi-linux-arm64.tar.gz \
- pi-windows-x64.zip \
- pi-windows-arm64.zip \
- --clobber
+ gh release upload "${RELEASE_TAG}" "${release_assets[@]}" --clobber
else
gh release create "${RELEASE_TAG}" \
--title "${RELEASE_TAG}" \
--notes-file /tmp/release-notes.md \
- pi-darwin-arm64.tar.gz \
- pi-darwin-x64.tar.gz \
- pi-linux-x64.tar.gz \
- pi-linux-arm64.tar.gz \
- pi-windows-x64.zip \
- pi-windows-arm64.zip
+ "${release_assets[@]}"
fi
publish-npm:
diff --git a/.github/workflows/issue-gate.yml b/.github/workflows/issue-gate.yml
index 62663940..d2bf830e 100644
--- a/.github/workflows/issue-gate.yml
+++ b/.github/workflows/issue-gate.yml
@@ -111,9 +111,17 @@ jobs:
body: message,
});
+ await github.rest.issues.addLabels({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ labels: ['untriaged'],
+ });
+
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
state: 'closed',
+ state_reason: 'not_planned',
});
diff --git a/.github/workflows/issue-triage-labels.yml b/.github/workflows/issue-triage-labels.yml
new file mode 100644
index 00000000..0e6204d5
--- /dev/null
+++ b/.github/workflows/issue-triage-labels.yml
@@ -0,0 +1,142 @@
+name: Issue Triage Labels
+
+on:
+ issues:
+ types: [reopened, labeled]
+
+jobs:
+ update-labels:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ steps:
+ - name: Update triage labels
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const UNTRIAGED_LABEL = 'untriaged';
+ const NO_ACTION_LABEL = 'no-action';
+ const LAST_READ_LABEL = 'last-read';
+ const TO_DISCUSS_LABEL = 'to-discuss';
+ const INPROGRESS_LABEL = 'inprogress';
+
+ function issueHasLabel(issue, labelName) {
+ return (issue.labels ?? []).some((label) => label.name === labelName);
+ }
+
+ async function removeLabelIfPresent(issueNumber, issue, labelName) {
+ if (!issueHasLabel(issue, labelName)) {
+ console.log(`Issue #${issueNumber} does not have ${labelName}`);
+ return;
+ }
+
+ try {
+ await github.rest.issues.removeLabel({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issueNumber,
+ name: labelName,
+ });
+ console.log(`Removed ${labelName} from #${issueNumber}`);
+ } catch (error) {
+ if (error.status === 404) {
+ console.log(`Label ${labelName} was already absent from #${issueNumber}`);
+ return;
+ }
+ throw error;
+ }
+ }
+
+ if (context.payload.action === 'reopened') {
+ await removeLabelIfPresent(context.issue.number, context.payload.issue, UNTRIAGED_LABEL);
+ await removeLabelIfPresent(context.issue.number, context.payload.issue, NO_ACTION_LABEL);
+ return;
+ }
+
+ if (context.payload.action === 'labeled' && context.payload.label?.name === NO_ACTION_LABEL) {
+ await removeLabelIfPresent(context.issue.number, context.payload.issue, UNTRIAGED_LABEL);
+ return;
+ }
+
+ if (context.payload.action !== 'labeled' || context.payload.label?.name !== LAST_READ_LABEL) {
+ console.log('Not a last-read label event');
+ return;
+ }
+
+ const currentIssueNumber = context.issue.number;
+ const lastReadIssues = await github.paginate(github.rest.issues.listForRepo, {
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ state: 'all',
+ labels: LAST_READ_LABEL,
+ per_page: 100,
+ });
+
+ const previousIssueNumbers = lastReadIssues
+ .filter((issue) => !issue.pull_request)
+ .map((issue) => issue.number)
+ .filter((issueNumber) => issueNumber !== currentIssueNumber);
+
+ if (previousIssueNumbers.length === 0) {
+ console.log('No previous last-read issue found');
+ return;
+ }
+
+ const previousIssueNumber = Math.max(...previousIssueNumbers);
+ if (currentIssueNumber <= previousIssueNumber) {
+ console.log(
+ `Last-read was added to old issue #${currentIssueNumber}; latest last-read is #${previousIssueNumber}`,
+ );
+ return;
+ }
+
+ const untriagedIssues = await github.paginate(github.rest.issues.listForRepo, {
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ state: 'all',
+ labels: UNTRIAGED_LABEL,
+ per_page: 100,
+ });
+
+ const issuesToMark = untriagedIssues
+ .filter((issue) => !issue.pull_request)
+ .filter((issue) => issue.number >= previousIssueNumber && issue.number <= currentIssueNumber)
+ .sort((a, b) => a.number - b.number);
+
+ if (issuesToMark.length === 0) {
+ console.log(`No untriaged issues found from #${previousIssueNumber} to #${currentIssueNumber}`);
+ return;
+ }
+
+ for (const issue of issuesToMark) {
+ if (issueHasLabel(issue, TO_DISCUSS_LABEL)) {
+ console.log(`Skipped ${NO_ACTION_LABEL} for #${issue.number} because it has ${TO_DISCUSS_LABEL}`);
+ } else {
+ await github.rest.issues.addLabels({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issue.number,
+ labels: [NO_ACTION_LABEL],
+ });
+ console.log(`Added ${NO_ACTION_LABEL} to #${issue.number}`);
+ }
+
+ await github.rest.issues.update({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issue.number,
+ state: 'closed',
+ state_reason: 'not_planned',
+ });
+ console.log(`Closed #${issue.number} as not planned`);
+
+ await removeLabelIfPresent(issue.number, issue, INPROGRESS_LABEL);
+
+ await github.rest.issues.removeLabel({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issue.number,
+ name: UNTRIAGED_LABEL,
+ });
+ console.log(`Removed ${UNTRIAGED_LABEL} from #${issue.number}`);
+ }
diff --git a/.github/workflows/remove-inprogress-on-close.yml b/.github/workflows/remove-inprogress-on-close.yml
new file mode 100644
index 00000000..e94b10e2
--- /dev/null
+++ b/.github/workflows/remove-inprogress-on-close.yml
@@ -0,0 +1,31 @@
+name: Remove In Progress Label On Close
+
+on:
+ issues:
+ types: [closed]
+
+jobs:
+ remove-label:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ steps:
+ - name: Remove inprogress label
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const labelName = 'inprogress';
+ const labels = context.payload.issue.labels ?? [];
+ const hasLabel = labels.some((label) => label.name === labelName);
+
+ if (!hasLabel) {
+ console.log(`Issue does not have ${labelName} label`);
+ return;
+ }
+
+ await github.rest.issues.removeLabel({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ name: labelName,
+ });
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 24f0f825..b0c38322 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,19 +2,27 @@
This guide exists to save both sides time.
+## Philosophy
+
+First things first: **pi's core is minimal**.
+
+If your feature does not belong in the core, it should be an extension. PRs that bloat the core will likely be rejected.
+
+Pi's core exists to be minimal and to be extensible so that it can be influenced and manipulated by extensions. Even hook points for extensions however should be well considered and discussed to avoid adding unmaintainable bloat and complex interactions.
+
## The One Rule
**You must understand your code.** If you cannot explain what your changes do and how they interact with the rest of the system, your PR will be closed.
Using AI to write code is fine. Submitting AI-generated slop without understanding it is not.
-If you use an agent, run it from the `pi-mono` root directory so it picks up `AGENTS.md` automatically. Your agent must follow the rules and guidelines in that file.
+If you use an agent, run it from the `pi` root directory so it picks up `AGENTS.md` automatically. Your agent must follow the rules and guidelines in that file.
## Contribution Gate
All issues and PRs from new contributors are auto-closed by default.
-Issues submitted Friday through Sunday are not reviewed. If something is urgent, ask on Discord: https://discord.com/invite/3cU7Bz4UPx
+Issues submitted Friday through Sunday are not guaranteed to be reviewed. If something is urgent, ask on Discord: https://discord.com/invite/3cU7Bz4UPx
Maintainers review auto-closed issues daily and reopen worthwhile ones. Issues that do not meet the quality bar below will not be reopened or receive a reply.
@@ -32,7 +40,7 @@ If you open an issue, you must use one of the two GitHub issue templates.
If you open an issue, keep it short, concrete, and worth reading.
- Keep it concise. If it does not fit on one screen, it is too long.
-- Write in your own voice.
+- Write in your own voice (do not use an LLM to generate text, if you must, follow up with a clearly AI labeled comment).
- State the bug or request clearly.
- Explain why it matters.
- If you want to implement the change yourself, say so.
@@ -62,10 +70,6 @@ Do not edit `CHANGELOG.md`. Changelog entries are added by maintainers.
If you are adding a new provider to `packages/ai`, see `AGENTS.md` for required tests.
-## Philosophy
-
-pi's core is minimal. If your feature does not belong in the core, it should be an extension. PRs that bloat the core will likely be rejected.
-
## Questions?
Ask on [Discord](https://discord.com/invite/nKXTsAcmbT).
@@ -76,9 +80,9 @@ Ask on [Discord](https://discord.com/invite/nKXTsAcmbT).
pi receives more issues than the maintainers can responsibly review in real time. Many reports do not meet the quality bar in this guide or do not follow CONTRIBUTING.md. Some are slung at the repository mindlessly via an agent instead of being reviewed and shaped by the person submitting them. Auto-closing creates a buffer so maintainers can review the tracker on their own schedule and reopen the issues that meet the quality bar.
-### Why are weekend issues not reviewed?
+### Why are weekend issues lower priority?
-Maintainers need uninterrupted time away from the issue tracker. Issues submitted Friday through Sunday are auto-closed and are not part of the Monday review queue. If a problem is urgent, ask on Discord and include the short version, a repro, and the relevant logs.
+We triage the tracker during working hours. That means more issues can accumulate over the weekend. Anything submitted Friday through Sunday may be missed or given lower priority in the Monday review queue. If a problem is urgent, ask on Discord and include the short version, a repro, and the relevant logs.
### Why do some issues get no reply?
@@ -91,3 +95,8 @@ AI can help group duplicates, summarize reports, and spot missing information. I
### Is this hostile to contributors?
No. It is a guardrail against burnout and tracker spam. Short, concrete, reproducible issues are welcome. Thoughtful contributions are welcome. Automated slop, entitlement, and large volumes of low-effort reports are not.
+
+## Where can I learn about plans?
+
+Earendil uses RFCs to discuss larger changes. Not all of them are public, but
+quite a few are. They can be found at [rfc.earendil.com](https://rfc.earendil.com/keyword/pi/).
diff --git a/README.md b/README.md
index 563b6858..130a412f 100644
--- a/README.md
+++ b/README.md
@@ -5,46 +5,24 @@
-
-
- pi.dev domain graciously donated by
-
- exe.dev
+
> New issues and PRs from new contributors are auto-closed by default. Maintainers review auto-closed issues daily. See [CONTRIBUTING.md](CONTRIBUTING.md).
----
+# Pi Agent Harness
-# Pi Agent Harness Mono Repo
-
-This is the home of the pi agent harness project including our self extensible coding agent.
+This is the home of the Pi agent harness project including our self extensible coding agent.
* **[@earendil-works/pi-coding-agent](packages/coding-agent)**: Interactive coding agent CLI
* **[@earendil-works/pi-agent-core](packages/agent)**: Agent runtime with tool calling and state management
* **[@earendil-works/pi-ai](packages/ai)**: Unified multi-provider LLM API (OpenAI, Anthropic, Google, …)
-To learn more about pi:
+To learn more about Pi:
* [Visit pi.dev](https://pi.dev), the project website with demos
* [Read the documentation](https://pi.dev/docs/latest), but you can also ask the agent to explain itself
-## Share your OSS coding agent sessions
-
-If you use pi or other coding agents for open source work, please share your sessions.
-
-Public OSS session data helps improve coding agents with real-world tasks, tool use, failures, and fixes instead of toy benchmarks.
-
-For the full explanation, see [this post on X](https://x.com/badlogicgames/status/2037811643774652911).
-
-To publish sessions, use [`badlogic/pi-share-hf`](https://github.com/badlogic/pi-share-hf). Read its README.md for setup instructions. All you need is a Hugging Face account, the Hugging Face CLI, and `pi-share-hf`.
-
-You can also watch [this video](https://x.com/badlogicgames/status/2041151967695634619), where I show how I publish my `pi-mono` sessions.
-
-I regularly publish my own `pi-mono` work sessions here:
-
-- [badlogicgames/pi-mono on Hugging Face](https://huggingface.co/datasets/badlogicgames/pi-mono)
-
## All Packages
| Package | Description |
@@ -62,13 +40,13 @@ Pi does not include a built-in permission system for restricting filesystem, pro
If you need stronger boundaries, containerize or sandbox Pi. See [packages/coding-agent/docs/containerization.md](packages/coding-agent/docs/containerization.md) for three patterns:
-- **OpenShell**: run the whole `pi` process in a policy-controlled sandbox.
- **Gondolin extension**: keep `pi` and provider auth on the host while routing built-in tools and `!` commands into a local Linux micro-VM.
- **Plain Docker**: run the whole `pi` process in a local container for simple isolation.
+- **OpenShell**: run the whole `pi` process in a policy-controlled sandbox.
## Contributing
-See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines and [AGENTS.md](AGENTS.md) for project-specific rules (for both humans and agents).
+See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines and [AGENTS.md](AGENTS.md) for project-specific rules (for both humans and agents). Longer term plans for Pi can also be found in [RFCs](https://rfc.earendil.com/keyword/pi/).
## Development
@@ -94,6 +72,28 @@ We treat npm dependency changes as reviewed code changes.
- CI installs with `npm ci --ignore-scripts`, and a scheduled GitHub workflow runs `npm audit --omit=dev` plus `npm audit signatures --omit=dev`.
- Shrinkwrap generation has an explicit allowlist for dependency lifecycle scripts; new lifecycle-script deps fail checks until reviewed.
+## Share your OSS coding agent sessions
+
+If you use Pi or other coding agents for open source work, please share your sessions.
+
+Public OSS session data helps improve coding agents with real-world tasks, tool use, failures, and fixes instead of toy benchmarks.
+
+For the full explanation, see [this post on X](https://x.com/badlogicgames/status/2037811643774652911).
+
+To publish sessions, use [`badlogic/pi-share-hf`](https://github.com/badlogic/pi-share-hf). Read its README.md for setup instructions. All you need is a Hugging Face account, the Hugging Face CLI, and `pi-share-hf`.
+
+You can also watch [this video](https://x.com/badlogicgames/status/2041151967695634619), where I show how I publish my `pi-mono` sessions.
+
+I regularly publish my own `pi-mono` work sessions here:
+
+- [badlogicgames/pi-mono on Hugging Face](https://huggingface.co/datasets/badlogicgames/pi-mono)
+
## License
MIT
+
+
+ pi.dev domain graciously donated by
+
+ exe.dev
+
diff --git a/package-lock.json b/package-lock.json
index b32c59fe..ba0eac39 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,7 +20,7 @@
"@biomejs/biome": "2.3.5",
"@types/node": "22.19.19",
"@typescript/native-preview": "7.0.0-dev.20260120.1",
- "esbuild": "0.28.0",
+ "esbuild": "0.28.1",
"husky": "9.1.7",
"jiti": "2.7.0",
"shx": "0.4.0",
@@ -31,20 +31,6 @@
"node": ">=22.19.0"
}
},
- "node_modules/@ampproject/remapping": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
- "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
"node_modules/@anthropic-ai/sandbox-runtime": {
"version": "0.0.26",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sandbox-runtime/-/sandbox-runtime-0.0.26.tgz",
@@ -811,10 +797,44 @@
"resolved": "packages/tui",
"link": true
},
+ "node_modules/@emnapi/core": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
+ "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.1",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
+ "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
+ "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz",
- "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
+ "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
"cpu": [
"ppc64"
],
@@ -829,9 +849,9 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz",
- "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
+ "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
"cpu": [
"arm"
],
@@ -846,9 +866,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz",
- "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
+ "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
"cpu": [
"arm64"
],
@@ -863,9 +883,9 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz",
- "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
+ "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
"cpu": [
"x64"
],
@@ -880,9 +900,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz",
- "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
+ "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
"cpu": [
"arm64"
],
@@ -897,9 +917,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz",
- "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
+ "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
"cpu": [
"x64"
],
@@ -914,9 +934,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz",
- "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
"cpu": [
"arm64"
],
@@ -931,9 +951,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz",
- "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
+ "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
"cpu": [
"x64"
],
@@ -948,9 +968,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz",
- "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
+ "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
"cpu": [
"arm"
],
@@ -965,9 +985,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz",
- "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
+ "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
"cpu": [
"arm64"
],
@@ -982,9 +1002,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz",
- "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
+ "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
"cpu": [
"ia32"
],
@@ -999,9 +1019,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz",
- "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
+ "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
"cpu": [
"loong64"
],
@@ -1016,9 +1036,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz",
- "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
+ "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
"cpu": [
"mips64el"
],
@@ -1033,9 +1053,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz",
- "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
+ "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
"cpu": [
"ppc64"
],
@@ -1050,9 +1070,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz",
- "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
+ "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
"cpu": [
"riscv64"
],
@@ -1067,9 +1087,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz",
- "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
+ "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
"cpu": [
"s390x"
],
@@ -1084,9 +1104,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz",
- "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
+ "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
"cpu": [
"x64"
],
@@ -1101,9 +1121,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz",
- "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
"cpu": [
"arm64"
],
@@ -1118,9 +1138,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz",
- "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
+ "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
"cpu": [
"x64"
],
@@ -1135,9 +1155,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz",
- "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
"cpu": [
"arm64"
],
@@ -1152,9 +1172,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz",
- "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
+ "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
"cpu": [
"x64"
],
@@ -1169,9 +1189,9 @@
}
},
"node_modules/@esbuild/openharmony-arm64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz",
- "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
+ "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
"cpu": [
"arm64"
],
@@ -1186,9 +1206,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz",
- "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
+ "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
"cpu": [
"x64"
],
@@ -1203,9 +1223,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz",
- "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
+ "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
"cpu": [
"arm64"
],
@@ -1220,9 +1240,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz",
- "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
+ "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
"cpu": [
"ia32"
],
@@ -1237,9 +1257,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz",
- "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
+ "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
"cpu": [
"x64"
],
@@ -1277,130 +1297,6 @@
}
}
},
- "node_modules/@isaacs/cliui": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
- "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^5.1.2",
- "string-width-cjs": "npm:string-width@^4.2.0",
- "strip-ansi": "^7.0.1",
- "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
- "wrap-ansi": "^8.1.0",
- "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
- "version": "6.2.2",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
- "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
- "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@isaacs/cliui/node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
- "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^6.2.2"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
- "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.1.0",
- "string-width": "^5.0.1",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/@istanbuljs/schema": {
- "version": "0.1.6",
- "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz",
- "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.13",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
- "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@@ -1624,14 +1520,42 @@
}
},
"node_modules/@mistralai/mistralai": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.1.tgz",
- "integrity": "sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==",
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.6.tgz",
+ "integrity": "sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==",
"license": "Apache-2.0",
"dependencies": {
+ "@opentelemetry/semantic-conventions": "^1.40.0",
"ws": "^8.18.0",
"zod": "^3.25.0 || ^4.0.0",
"zod-to-json-schema": "^3.25.0"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.9.0"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz",
+ "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tybys/wasm-util": "^0.10.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1"
}
},
"node_modules/@nodable/entities": {
@@ -1684,17 +1608,34 @@
"node": ">= 8"
}
},
- "node_modules/@pkgjs/parseargs": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
- "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
+ "node_modules/@opentelemetry/api": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
+ "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/@opentelemetry/semantic-conventions": {
+ "version": "1.41.1",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz",
+ "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==",
+ "license": "Apache-2.0",
"engines": {
"node": ">=14"
}
},
+ "node_modules/@oxc-project/types": {
+ "version": "0.133.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
+ "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
"node_modules/@pondwader/socks5-server": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@pondwader/socks5-server/-/socks5-server-1.0.10.tgz",
@@ -1720,9 +1661,9 @@
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/eventemitter": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
- "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
+ "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/fetch": {
@@ -1740,12 +1681,6 @@
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
"license": "BSD-3-Clause"
},
- "node_modules/@protobufjs/inquire": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz",
- "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==",
- "license": "BSD-3-Clause"
- },
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
@@ -1764,24 +1699,10 @@
"integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==",
"license": "BSD-3-Clause"
},
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz",
- "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz",
- "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==",
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz",
+ "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==",
"cpu": [
"arm64"
],
@@ -1790,12 +1711,15 @@
"optional": true,
"os": [
"android"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz",
- "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==",
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz",
+ "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==",
"cpu": [
"arm64"
],
@@ -1804,12 +1728,15 @@
"optional": true,
"os": [
"darwin"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz",
- "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==",
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz",
+ "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==",
"cpu": [
"x64"
],
@@ -1818,26 +1745,15 @@
"optional": true,
"os": [
"darwin"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz",
- "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==",
- "cpu": [
- "arm64"
],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz",
- "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==",
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz",
+ "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==",
"cpu": [
"x64"
],
@@ -1846,12 +1762,15 @@
"optional": true,
"os": [
"freebsd"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz",
- "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==",
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz",
+ "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==",
"cpu": [
"arm"
],
@@ -1860,194 +1779,135 @@
"optional": true,
"os": [
"linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz",
- "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==",
- "cpu": [
- "arm"
],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz",
- "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==",
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz",
+ "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==",
"cpu": [
"arm64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
"linux"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz",
- "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==",
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz",
+ "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==",
"cpu": [
"arm64"
],
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz",
- "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==",
- "cpu": [
- "loong64"
+ "libc": [
+ "musl"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loong64-musl": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz",
- "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==",
- "cpu": [
- "loong64"
],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz",
- "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==",
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz",
+ "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==",
"cpu": [
"ppc64"
],
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-ppc64-musl": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz",
- "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==",
- "cpu": [
- "ppc64"
+ "libc": [
+ "glibc"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz",
- "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==",
- "cpu": [
- "riscv64"
],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz",
- "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz",
- "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==",
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz",
+ "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==",
"cpu": [
"s390x"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
"linux"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz",
- "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==",
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz",
+ "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==",
"cpu": [
"x64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
"linux"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz",
- "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==",
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz",
+ "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==",
"cpu": [
"x64"
],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
"optional": true,
"os": [
"linux"
- ]
- },
- "node_modules/@rollup/rollup-openbsd-x64": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz",
- "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==",
- "cpu": [
- "x64"
],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ]
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz",
- "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==",
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz",
+ "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==",
"cpu": [
"arm64"
],
@@ -2056,12 +1916,34 @@
"optional": true,
"os": [
"openharmony"
- ]
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz",
- "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==",
+ "node_modules/@rolldown/binding-wasm32-wasi": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz",
+ "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "1.10.0",
+ "@emnapi/runtime": "1.10.0",
+ "@napi-rs/wasm-runtime": "^1.1.4"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz",
+ "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==",
"cpu": [
"arm64"
],
@@ -2070,26 +1952,15 @@
"optional": true,
"os": [
"win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz",
- "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==",
- "cpu": [
- "ia32"
],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
},
- "node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz",
- "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==",
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz",
+ "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==",
"cpu": [
"x64"
],
@@ -2098,21 +1969,17 @@
"optional": true,
"os": [
"win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz",
- "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==",
- "cpu": [
- "x64"
],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
+ "license": "MIT"
},
"node_modules/@silvia-odwyer/photon-node": {
"version": "0.3.4",
@@ -2240,6 +2107,24 @@
"node": ">=14.0.0"
}
},
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
+ "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@types/chai": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
@@ -2337,6 +2222,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/semver": {
+ "version": "7.7.1",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz",
+ "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@typescript/native-preview": {
"version": "7.0.0-dev.20260120.1",
"resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20260120.1.tgz",
@@ -2454,155 +2346,6 @@
"win32"
]
},
- "node_modules/@vitest/coverage-v8": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz",
- "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@ampproject/remapping": "^2.3.0",
- "@bcoe/v8-coverage": "^1.0.2",
- "ast-v8-to-istanbul": "^0.3.3",
- "debug": "^4.4.1",
- "istanbul-lib-coverage": "^3.2.2",
- "istanbul-lib-report": "^3.0.1",
- "istanbul-lib-source-maps": "^5.0.6",
- "istanbul-reports": "^3.1.7",
- "magic-string": "^0.30.17",
- "magicast": "^0.3.5",
- "std-env": "^3.9.0",
- "test-exclude": "^7.0.1",
- "tinyrainbow": "^2.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- },
- "peerDependencies": {
- "@vitest/browser": "3.2.4",
- "vitest": "3.2.4"
- },
- "peerDependenciesMeta": {
- "@vitest/browser": {
- "optional": true
- }
- }
- },
- "node_modules/@vitest/expect": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
- "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/chai": "^5.2.2",
- "@vitest/spy": "3.2.4",
- "@vitest/utils": "3.2.4",
- "chai": "^5.2.0",
- "tinyrainbow": "^2.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/mocker": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz",
- "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/spy": "3.2.4",
- "estree-walker": "^3.0.3",
- "magic-string": "^0.30.17"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- },
- "peerDependencies": {
- "msw": "^2.4.9",
- "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
- },
- "peerDependenciesMeta": {
- "msw": {
- "optional": true
- },
- "vite": {
- "optional": true
- }
- }
- },
- "node_modules/@vitest/pretty-format": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz",
- "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "tinyrainbow": "^2.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/runner": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz",
- "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/utils": "3.2.4",
- "pathe": "^2.0.3",
- "strip-literal": "^3.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/snapshot": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz",
- "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/pretty-format": "3.2.4",
- "magic-string": "^0.30.17",
- "pathe": "^2.0.3"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/spy": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz",
- "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "tinyspy": "^4.0.3"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/utils": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz",
- "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/pretty-format": "3.2.4",
- "loupe": "^3.1.4",
- "tinyrainbow": "^2.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
"node_modules/@xterm/headless": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.5.0.tgz",
@@ -2619,32 +2362,6 @@
"node": ">= 14"
}
},
- "node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
@@ -2664,18 +2381,6 @@
"node": ">=12"
}
},
- "node_modules/ast-v8-to-istanbul": {
- "version": "0.3.12",
- "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz",
- "integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/trace-mapping": "^0.3.31",
- "estree-walker": "^3.0.3",
- "js-tokens": "^10.0.0"
- }
- },
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@@ -2806,16 +2511,6 @@
"node": ">=10.0.0"
}
},
- "node_modules/cac": {
- "version": "6.7.14",
- "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
- "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/canvas": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/canvas/-/canvas-3.2.3.tgz",
@@ -2843,23 +2538,6 @@
"node": ">=20"
}
},
- "node_modules/chai": {
- "version": "5.3.3",
- "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
- "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "assertion-error": "^2.0.1",
- "check-error": "^2.1.1",
- "deep-eql": "^5.0.1",
- "loupe": "^3.1.0",
- "pathval": "^2.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
"node_modules/chalk": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
@@ -2872,16 +2550,6 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
- "node_modules/check-error": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
- "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 16"
- }
- },
"node_modules/chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
@@ -2889,26 +2557,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/commander": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
@@ -2918,6 +2566,13 @@
"node": ">=18"
}
},
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cpu-features": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz",
@@ -2988,16 +2643,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/deep-eql": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
- "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
@@ -3027,13 +2672,6 @@
"node": ">=0.3.1"
}
},
- "node_modules/eastasianwidth": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
@@ -3043,13 +2681,6 @@
"safe-buffer": "^5.0.1"
}
},
- "node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
@@ -3070,17 +2701,10 @@
"node": ">= 0.4"
}
},
- "node_modules/es-module-lexer": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
- "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/esbuild": {
- "version": "0.28.0",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz",
- "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==",
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
+ "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -3091,32 +2715,32 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.28.0",
- "@esbuild/android-arm": "0.28.0",
- "@esbuild/android-arm64": "0.28.0",
- "@esbuild/android-x64": "0.28.0",
- "@esbuild/darwin-arm64": "0.28.0",
- "@esbuild/darwin-x64": "0.28.0",
- "@esbuild/freebsd-arm64": "0.28.0",
- "@esbuild/freebsd-x64": "0.28.0",
- "@esbuild/linux-arm": "0.28.0",
- "@esbuild/linux-arm64": "0.28.0",
- "@esbuild/linux-ia32": "0.28.0",
- "@esbuild/linux-loong64": "0.28.0",
- "@esbuild/linux-mips64el": "0.28.0",
- "@esbuild/linux-ppc64": "0.28.0",
- "@esbuild/linux-riscv64": "0.28.0",
- "@esbuild/linux-s390x": "0.28.0",
- "@esbuild/linux-x64": "0.28.0",
- "@esbuild/netbsd-arm64": "0.28.0",
- "@esbuild/netbsd-x64": "0.28.0",
- "@esbuild/openbsd-arm64": "0.28.0",
- "@esbuild/openbsd-x64": "0.28.0",
- "@esbuild/openharmony-arm64": "0.28.0",
- "@esbuild/sunos-x64": "0.28.0",
- "@esbuild/win32-arm64": "0.28.0",
- "@esbuild/win32-ia32": "0.28.0",
- "@esbuild/win32-x64": "0.28.0"
+ "@esbuild/aix-ppc64": "0.28.1",
+ "@esbuild/android-arm": "0.28.1",
+ "@esbuild/android-arm64": "0.28.1",
+ "@esbuild/android-x64": "0.28.1",
+ "@esbuild/darwin-arm64": "0.28.1",
+ "@esbuild/darwin-x64": "0.28.1",
+ "@esbuild/freebsd-arm64": "0.28.1",
+ "@esbuild/freebsd-x64": "0.28.1",
+ "@esbuild/linux-arm": "0.28.1",
+ "@esbuild/linux-arm64": "0.28.1",
+ "@esbuild/linux-ia32": "0.28.1",
+ "@esbuild/linux-loong64": "0.28.1",
+ "@esbuild/linux-mips64el": "0.28.1",
+ "@esbuild/linux-ppc64": "0.28.1",
+ "@esbuild/linux-riscv64": "0.28.1",
+ "@esbuild/linux-s390x": "0.28.1",
+ "@esbuild/linux-x64": "0.28.1",
+ "@esbuild/netbsd-arm64": "0.28.1",
+ "@esbuild/netbsd-x64": "0.28.1",
+ "@esbuild/openbsd-arm64": "0.28.1",
+ "@esbuild/openbsd-x64": "0.28.1",
+ "@esbuild/openharmony-arm64": "0.28.1",
+ "@esbuild/sunos-x64": "0.28.1",
+ "@esbuild/win32-arm64": "0.28.1",
+ "@esbuild/win32-ia32": "0.28.1",
+ "@esbuild/win32-x64": "0.28.1"
}
},
"node_modules/estree-walker": {
@@ -3347,36 +2971,6 @@
"node": ">=8"
}
},
- "node_modules/foreground-child": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
- "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "cross-spawn": "^7.0.6",
- "signal-exit": "^4.0.1"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/foreground-child/node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
@@ -3716,16 +3310,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -3803,21 +3387,6 @@
"node": ">=8"
}
},
- "node_modules/istanbul-lib-source-maps": {
- "version": "5.0.6",
- "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
- "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@jridgewell/trace-mapping": "^0.3.23",
- "debug": "^4.1.1",
- "istanbul-lib-coverage": "^3.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/istanbul-reports": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
@@ -3832,22 +3401,6 @@
"node": ">=8"
}
},
- "node_modules/jackspeak": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
- "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "@isaacs/cliui": "^8.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- },
- "optionalDependencies": {
- "@pkgjs/parseargs": "^0.11.0"
- }
- },
"node_modules/jiti": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
@@ -3907,6 +3460,279 @@
"safe-buffer": "^5.0.1"
}
},
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
"node_modules/lodash-es": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
@@ -3919,13 +3745,6 @@
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
},
- "node_modules/loupe": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
- "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/lru-cache": {
"version": "11.4.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.4.0.tgz",
@@ -3945,18 +3764,6 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
- "node_modules/magicast": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz",
- "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.25.4",
- "@babel/types": "^7.25.4",
- "source-map-js": "^1.2.0"
- }
- },
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
@@ -3974,15 +3781,15 @@
}
},
"node_modules/marked": {
- "version": "15.0.12",
- "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz",
- "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
+ "version": "18.0.5",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz",
+ "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
- "node": ">= 18"
+ "node": ">= 20"
}
},
"node_modules/merge2": {
@@ -4199,6 +4006,20 @@
"node": ">=4"
}
},
+ "node_modules/obug": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz",
+ "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -4259,13 +4080,6 @@
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
"license": "MIT"
},
- "node_modules/package-json-from-dist": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
- "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
- "dev": true,
- "license": "BlueOak-1.0.0"
- },
"node_modules/partial-json": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz",
@@ -4326,16 +4140,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/pathval": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
- "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 14.16"
- }
- },
"node_modules/pi-extension-custom-provider-anthropic": {
"resolved": "packages/coding-agent/examples/extensions/custom-provider-anthropic",
"link": true
@@ -4377,9 +4181,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.14",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
- "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
+ "version": "8.5.15",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
+ "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
"dev": true,
"funding": [
{
@@ -4397,7 +4201,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.11",
+ "nanoid": "^3.3.12",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -4454,24 +4258,23 @@
}
},
"node_modules/protobufjs": {
- "version": "7.5.9",
- "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.9.tgz",
- "integrity": "sha512-Od4muIm3HW1AouyHF5lONOf1FWo3hY1NbFDoy191X9GzhpgW1clCoaFjfVs2rKJNFYpTNJbje4cbAIDBZJ63ZA==",
+ "version": "7.6.4",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz",
+ "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.5",
- "@protobufjs/eventemitter": "^1.1.0",
+ "@protobufjs/eventemitter": "^1.1.1",
"@protobufjs/fetch": "^1.1.1",
"@protobufjs/float": "^1.0.2",
- "@protobufjs/inquire": "^1.1.2",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.1",
"@types/node": ">=13.7.0",
- "long": "^5.0.0"
+ "long": "^5.3.2"
},
"engines": {
"node": ">=12.0.0"
@@ -4594,58 +4397,40 @@
"node": ">=0.10.0"
}
},
- "node_modules/rollup": {
- "version": "4.60.4",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz",
- "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==",
+ "node_modules/rolldown": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz",
+ "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/estree": "1.0.8"
+ "@oxc-project/types": "=0.133.0",
+ "@rolldown/pluginutils": "^1.0.0"
},
"bin": {
- "rollup": "dist/bin/rollup"
+ "rolldown": "bin/cli.mjs"
},
"engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
+ "node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.60.4",
- "@rollup/rollup-android-arm64": "4.60.4",
- "@rollup/rollup-darwin-arm64": "4.60.4",
- "@rollup/rollup-darwin-x64": "4.60.4",
- "@rollup/rollup-freebsd-arm64": "4.60.4",
- "@rollup/rollup-freebsd-x64": "4.60.4",
- "@rollup/rollup-linux-arm-gnueabihf": "4.60.4",
- "@rollup/rollup-linux-arm-musleabihf": "4.60.4",
- "@rollup/rollup-linux-arm64-gnu": "4.60.4",
- "@rollup/rollup-linux-arm64-musl": "4.60.4",
- "@rollup/rollup-linux-loong64-gnu": "4.60.4",
- "@rollup/rollup-linux-loong64-musl": "4.60.4",
- "@rollup/rollup-linux-ppc64-gnu": "4.60.4",
- "@rollup/rollup-linux-ppc64-musl": "4.60.4",
- "@rollup/rollup-linux-riscv64-gnu": "4.60.4",
- "@rollup/rollup-linux-riscv64-musl": "4.60.4",
- "@rollup/rollup-linux-s390x-gnu": "4.60.4",
- "@rollup/rollup-linux-x64-gnu": "4.60.4",
- "@rollup/rollup-linux-x64-musl": "4.60.4",
- "@rollup/rollup-openbsd-x64": "4.60.4",
- "@rollup/rollup-openharmony-arm64": "4.60.4",
- "@rollup/rollup-win32-arm64-msvc": "4.60.4",
- "@rollup/rollup-win32-ia32-msvc": "4.60.4",
- "@rollup/rollup-win32-x64-gnu": "4.60.4",
- "@rollup/rollup-win32-x64-msvc": "4.60.4",
- "fsevents": "~2.3.2"
+ "@rolldown/binding-android-arm64": "1.0.3",
+ "@rolldown/binding-darwin-arm64": "1.0.3",
+ "@rolldown/binding-darwin-x64": "1.0.3",
+ "@rolldown/binding-freebsd-x64": "1.0.3",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.3",
+ "@rolldown/binding-linux-arm64-gnu": "1.0.3",
+ "@rolldown/binding-linux-arm64-musl": "1.0.3",
+ "@rolldown/binding-linux-ppc64-gnu": "1.0.3",
+ "@rolldown/binding-linux-s390x-gnu": "1.0.3",
+ "@rolldown/binding-linux-x64-gnu": "1.0.3",
+ "@rolldown/binding-linux-x64-musl": "1.0.3",
+ "@rolldown/binding-openharmony-arm64": "1.0.3",
+ "@rolldown/binding-wasm32-wasi": "1.0.3",
+ "@rolldown/binding-win32-arm64-msvc": "1.0.3",
+ "@rolldown/binding-win32-x64-msvc": "1.0.3"
}
},
- "node_modules/rollup/node_modules/@types/estree": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -4700,7 +4485,6 @@
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
- "dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -4872,13 +4656,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/std-env": {
- "version": "3.10.0",
- "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
- "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -4889,64 +4666,6 @@
"safe-buffer": "~5.2.0"
}
},
- "node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/string-width-cjs": {
- "name": "string-width",
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi-cjs": {
- "name": "strip-ansi",
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/strip-eof": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
@@ -4967,26 +4686,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/strip-literal": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz",
- "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "js-tokens": "^9.0.1"
- },
- "funding": {
- "url": "https://github.com/sponsors/antfu"
- }
- },
- "node_modules/strip-literal/node_modules/js-tokens": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
- "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/strnum": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz",
@@ -5042,100 +4741,6 @@
"node": ">=6"
}
},
- "node_modules/test-exclude": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz",
- "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@istanbuljs/schema": "^0.1.2",
- "glob": "^10.4.1",
- "minimatch": "^10.2.2"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/test-exclude/node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/test-exclude/node_modules/brace-expansion": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
- "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/test-exclude/node_modules/glob": {
- "version": "10.5.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
- "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
- "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/test-exclude/node_modules/glob/node_modules/minimatch": {
- "version": "9.0.9",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
- "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.2"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/test-exclude/node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/test-exclude/node_modules/path-scurry": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
- "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^10.2.0",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
- },
- "engines": {
- "node": ">=16 || 14 >=14.18"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/tinybench": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
@@ -5143,17 +4748,10 @@
"dev": true,
"license": "MIT"
},
- "node_modules/tinyexec": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
- "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/tinyglobby": {
- "version": "0.2.16",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
- "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5198,36 +4796,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/tinypool": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
- "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.0.0 || >=20.0.0"
- }
- },
- "node_modules/tinyrainbow": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
- "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/tinyspy": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz",
- "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14.0.0"
- }
- },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -5312,9 +4880,9 @@
}
},
"node_modules/undici": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-8.3.0.tgz",
- "integrity": "sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q==",
+ "version": "8.5.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-8.5.0.tgz",
+ "integrity": "sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==",
"license": "MIT",
"engines": {
"node": ">=22.19.0"
@@ -5334,18 +4902,17 @@
"license": "MIT"
},
"node_modules/vite": {
- "version": "7.3.3",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz",
- "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==",
+ "version": "8.0.16",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz",
+ "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "esbuild": "^0.27.0",
- "fdir": "^6.5.0",
- "picomatch": "^4.0.3",
- "postcss": "^8.5.6",
- "rollup": "^4.43.0",
- "tinyglobby": "^0.2.15"
+ "lightningcss": "^1.32.0",
+ "picomatch": "^4.0.4",
+ "postcss": "^8.5.15",
+ "rolldown": "1.0.3",
+ "tinyglobby": "^0.2.17"
},
"bin": {
"vite": "bin/vite.js"
@@ -5361,9 +4928,10 @@
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.1.18",
+ "esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
- "lightningcss": "^1.21.0",
"sass": "^1.70.0",
"sass-embedded": "^1.70.0",
"stylus": ">=0.54.8",
@@ -5376,15 +4944,18 @@
"@types/node": {
"optional": true
},
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
"jiti": {
"optional": true
},
"less": {
"optional": true
},
- "lightningcss": {
- "optional": true
- },
"sass": {
"optional": true
},
@@ -5408,531 +4979,6 @@
}
}
},
- "node_modules/vite-node": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz",
- "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cac": "^6.7.14",
- "debug": "^4.4.1",
- "es-module-lexer": "^1.7.0",
- "pathe": "^2.0.3",
- "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
- },
- "bin": {
- "vite-node": "vite-node.mjs"
- },
- "engines": {
- "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/vite/node_modules/@esbuild/aix-ppc64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
- "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/android-arm": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
- "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/android-arm64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
- "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/android-x64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
- "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/darwin-arm64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
- "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/darwin-x64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
- "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
- "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/freebsd-x64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
- "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-arm": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
- "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-arm64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
- "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-ia32": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
- "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-loong64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
- "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-mips64el": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
- "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-ppc64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
- "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-riscv64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
- "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-s390x": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
- "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/linux-x64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
- "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/netbsd-arm64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
- "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/netbsd-x64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
- "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/openbsd-arm64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
- "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/openbsd-x64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
- "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/openharmony-arm64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
- "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/sunos-x64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
- "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/win32-arm64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
- "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/win32-ia32": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
- "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/@esbuild/win32-x64": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
- "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/vite/node_modules/esbuild": {
- "version": "0.27.7",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
- "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.27.7",
- "@esbuild/android-arm": "0.27.7",
- "@esbuild/android-arm64": "0.27.7",
- "@esbuild/android-x64": "0.27.7",
- "@esbuild/darwin-arm64": "0.27.7",
- "@esbuild/darwin-x64": "0.27.7",
- "@esbuild/freebsd-arm64": "0.27.7",
- "@esbuild/freebsd-x64": "0.27.7",
- "@esbuild/linux-arm": "0.27.7",
- "@esbuild/linux-arm64": "0.27.7",
- "@esbuild/linux-ia32": "0.27.7",
- "@esbuild/linux-loong64": "0.27.7",
- "@esbuild/linux-mips64el": "0.27.7",
- "@esbuild/linux-ppc64": "0.27.7",
- "@esbuild/linux-riscv64": "0.27.7",
- "@esbuild/linux-s390x": "0.27.7",
- "@esbuild/linux-x64": "0.27.7",
- "@esbuild/netbsd-arm64": "0.27.7",
- "@esbuild/netbsd-x64": "0.27.7",
- "@esbuild/openbsd-arm64": "0.27.7",
- "@esbuild/openbsd-x64": "0.27.7",
- "@esbuild/openharmony-arm64": "0.27.7",
- "@esbuild/sunos-x64": "0.27.7",
- "@esbuild/win32-arm64": "0.27.7",
- "@esbuild/win32-ia32": "0.27.7",
- "@esbuild/win32-x64": "0.27.7"
- }
- },
- "node_modules/vite/node_modules/fdir": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12.0.0"
- },
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
- }
- },
"node_modules/vite/node_modules/picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
@@ -5946,92 +4992,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/vitest": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
- "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/chai": "^5.2.2",
- "@vitest/expect": "3.2.4",
- "@vitest/mocker": "3.2.4",
- "@vitest/pretty-format": "^3.2.4",
- "@vitest/runner": "3.2.4",
- "@vitest/snapshot": "3.2.4",
- "@vitest/spy": "3.2.4",
- "@vitest/utils": "3.2.4",
- "chai": "^5.2.0",
- "debug": "^4.4.1",
- "expect-type": "^1.2.1",
- "magic-string": "^0.30.17",
- "pathe": "^2.0.3",
- "picomatch": "^4.0.2",
- "std-env": "^3.9.0",
- "tinybench": "^2.9.0",
- "tinyexec": "^0.3.2",
- "tinyglobby": "^0.2.14",
- "tinypool": "^1.1.1",
- "tinyrainbow": "^2.0.0",
- "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
- "vite-node": "3.2.4",
- "why-is-node-running": "^2.3.0"
- },
- "bin": {
- "vitest": "vitest.mjs"
- },
- "engines": {
- "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- },
- "peerDependencies": {
- "@edge-runtime/vm": "*",
- "@types/debug": "^4.1.12",
- "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
- "@vitest/browser": "3.2.4",
- "@vitest/ui": "3.2.4",
- "happy-dom": "*",
- "jsdom": "*"
- },
- "peerDependenciesMeta": {
- "@edge-runtime/vm": {
- "optional": true
- },
- "@types/debug": {
- "optional": true
- },
- "@types/node": {
- "optional": true
- },
- "@vitest/browser": {
- "optional": true
- },
- "@vitest/ui": {
- "optional": true
- },
- "happy-dom": {
- "optional": true
- },
- "jsdom": {
- "optional": true
- }
- }
- },
- "node_modules/vitest/node_modules/picomatch": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
@@ -6073,25 +5033,6 @@
"node": ">=8"
}
},
- "node_modules/wrap-ansi-cjs": {
- "name": "wrap-ansi",
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -6100,9 +5041,9 @@
"license": "ISC"
},
"node_modules/ws": {
- "version": "8.20.1",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz",
- "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==",
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
+ "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
@@ -6170,19 +5111,19 @@
},
"packages/agent": {
"name": "@earendil-works/pi-agent-core",
- "version": "0.79.1",
+ "version": "0.79.10",
"license": "MIT",
"dependencies": {
- "@earendil-works/pi-ai": "^0.79.1",
+ "@earendil-works/pi-ai": "^0.79.10",
"ignore": "7.0.5",
"typebox": "1.1.38",
"yaml": "2.9.0"
},
"devDependencies": {
"@types/node": "24.12.4",
- "@vitest/coverage-v8": "3.2.4",
+ "@vitest/coverage-v8": "4.1.9",
"typescript": "5.9.3",
- "vitest": "3.2.4"
+ "vitest": "4.1.9"
},
"engines": {
"node": ">=22.19.0"
@@ -6198,6 +5139,204 @@
"undici-types": "~7.16.0"
}
},
+ "packages/agent/node_modules/@vitest/coverage-v8": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.9.tgz",
+ "integrity": "sha512-G9/lgqibheLVBDRuya45EbsEXTYcWoSG+TLg7i2axuzx0Eq62eXn+aWXyaVdV5vKvFSWd6ywcX8hA7la9Pvu8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@bcoe/v8-coverage": "^1.0.2",
+ "@vitest/utils": "4.1.9",
+ "ast-v8-to-istanbul": "^1.0.0",
+ "istanbul-lib-coverage": "^3.2.2",
+ "istanbul-lib-report": "^3.0.1",
+ "istanbul-reports": "^3.2.0",
+ "magicast": "^0.5.2",
+ "obug": "^2.1.1",
+ "std-env": "^4.0.0-rc.1",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@vitest/browser": "4.1.9",
+ "vitest": "4.1.9"
+ },
+ "peerDependenciesMeta": {
+ "@vitest/browser": {
+ "optional": true
+ }
+ }
+ },
+ "packages/agent/node_modules/@vitest/expect": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz",
+ "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.1.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.1.9",
+ "@vitest/utils": "4.1.9",
+ "chai": "^6.2.2",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "packages/agent/node_modules/@vitest/pretty-format": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz",
+ "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "packages/agent/node_modules/@vitest/runner": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz",
+ "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.1.9",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "packages/agent/node_modules/@vitest/snapshot": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz",
+ "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.9",
+ "@vitest/utils": "4.1.9",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "packages/agent/node_modules/@vitest/spy": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz",
+ "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "packages/agent/node_modules/@vitest/utils": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz",
+ "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.9",
+ "convert-source-map": "^2.0.0",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "packages/agent/node_modules/ast-v8-to-istanbul": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.4.tgz",
+ "integrity": "sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.31",
+ "estree-walker": "^3.0.3",
+ "js-tokens": "^10.0.0"
+ }
+ },
+ "packages/agent/node_modules/chai": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "packages/agent/node_modules/es-module-lexer": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
+ "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "packages/agent/node_modules/magicast": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz",
+ "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.3",
+ "@babel/types": "^7.29.0",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "packages/agent/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "packages/agent/node_modules/std-env": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
+ "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "packages/agent/node_modules/tinyexec": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
+ "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "packages/agent/node_modules/tinyrainbow": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
+ "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"packages/agent/node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
@@ -6205,15 +5344,133 @@
"dev": true,
"license": "MIT"
},
+ "packages/agent/node_modules/vitest": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz",
+ "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "4.1.9",
+ "@vitest/mocker": "4.1.9",
+ "@vitest/pretty-format": "4.1.9",
+ "@vitest/runner": "4.1.9",
+ "@vitest/snapshot": "4.1.9",
+ "@vitest/spy": "4.1.9",
+ "@vitest/utils": "4.1.9",
+ "es-module-lexer": "^2.0.0",
+ "expect-type": "^1.3.0",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.1",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^4.0.0-rc.1",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^1.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.1.0",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@opentelemetry/api": "^1.9.0",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.1.9",
+ "@vitest/browser-preview": "4.1.9",
+ "@vitest/browser-webdriverio": "4.1.9",
+ "@vitest/coverage-istanbul": "4.1.9",
+ "@vitest/coverage-v8": "4.1.9",
+ "@vitest/ui": "4.1.9",
+ "happy-dom": "*",
+ "jsdom": "*",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/coverage-istanbul": {
+ "optional": true
+ },
+ "@vitest/coverage-v8": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ },
+ "vite": {
+ "optional": false
+ }
+ }
+ },
+ "packages/agent/node_modules/vitest/node_modules/@vitest/mocker": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz",
+ "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "4.1.9",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
"packages/ai": {
"name": "@earendil-works/pi-ai",
- "version": "0.79.1",
+ "version": "0.79.10",
"license": "MIT",
"dependencies": {
"@anthropic-ai/sdk": "0.91.1",
"@aws-sdk/client-bedrock-runtime": "3.1048.0",
"@google/genai": "1.52.0",
- "@mistralai/mistralai": "2.2.1",
+ "@mistralai/mistralai": "2.2.6",
+ "@opentelemetry/api": "1.9.0",
"@smithy/node-http-handler": "4.7.3",
"http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.6",
@@ -6227,7 +5484,7 @@
"devDependencies": {
"@types/node": "24.12.4",
"canvas": "3.2.3",
- "vitest": "3.2.4"
+ "vitest": "4.1.9"
},
"engines": {
"node": ">=22.19.0"
@@ -6243,6 +5500,176 @@
"undici-types": "~7.16.0"
}
},
+ "packages/ai/node_modules/@vitest/expect": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz",
+ "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.1.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.1.9",
+ "@vitest/utils": "4.1.9",
+ "chai": "^6.2.2",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "packages/ai/node_modules/@vitest/mocker": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz",
+ "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "4.1.9",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "packages/ai/node_modules/@vitest/pretty-format": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz",
+ "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "packages/ai/node_modules/@vitest/runner": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz",
+ "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.1.9",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "packages/ai/node_modules/@vitest/snapshot": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz",
+ "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.9",
+ "@vitest/utils": "4.1.9",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "packages/ai/node_modules/@vitest/spy": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz",
+ "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "packages/ai/node_modules/@vitest/utils": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz",
+ "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.9",
+ "convert-source-map": "^2.0.0",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "packages/ai/node_modules/chai": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "packages/ai/node_modules/es-module-lexer": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
+ "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "packages/ai/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "packages/ai/node_modules/std-env": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
+ "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "packages/ai/node_modules/tinyexec": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
+ "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "packages/ai/node_modules/tinyrainbow": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
+ "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"packages/ai/node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
@@ -6250,14 +5677,104 @@
"dev": true,
"license": "MIT"
},
- "packages/coding-agent": {
- "name": "@earendil-works/pi-coding-agent",
- "version": "0.79.1",
+ "packages/ai/node_modules/vitest": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz",
+ "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@earendil-works/pi-agent-core": "^0.79.1",
- "@earendil-works/pi-ai": "^0.79.1",
- "@earendil-works/pi-tui": "^0.79.1",
+ "@vitest/expect": "4.1.9",
+ "@vitest/mocker": "4.1.9",
+ "@vitest/pretty-format": "4.1.9",
+ "@vitest/runner": "4.1.9",
+ "@vitest/snapshot": "4.1.9",
+ "@vitest/spy": "4.1.9",
+ "@vitest/utils": "4.1.9",
+ "es-module-lexer": "^2.0.0",
+ "expect-type": "^1.3.0",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.1",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^4.0.0-rc.1",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^1.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.1.0",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@opentelemetry/api": "^1.9.0",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.1.9",
+ "@vitest/browser-preview": "4.1.9",
+ "@vitest/browser-webdriverio": "4.1.9",
+ "@vitest/coverage-istanbul": "4.1.9",
+ "@vitest/coverage-v8": "4.1.9",
+ "@vitest/ui": "4.1.9",
+ "happy-dom": "*",
+ "jsdom": "*",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/coverage-istanbul": {
+ "optional": true
+ },
+ "@vitest/coverage-v8": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ },
+ "vite": {
+ "optional": false
+ }
+ }
+ },
+ "packages/coding-agent": {
+ "name": "@earendil-works/pi-coding-agent",
+ "version": "0.79.10",
+ "license": "MIT",
+ "dependencies": {
+ "@earendil-works/pi-agent-core": "^0.79.10",
+ "@earendil-works/pi-ai": "^0.79.10",
+ "@earendil-works/pi-tui": "^0.79.10",
"@silvia-odwyer/photon-node": "0.3.4",
"chalk": "5.6.2",
"cross-spawn": "7.0.6",
@@ -6269,8 +5786,9 @@
"jiti": "2.7.0",
"minimatch": "10.2.5",
"proper-lockfile": "4.1.2",
+ "semver": "7.8.0",
"typebox": "1.1.38",
- "undici": "8.3.0",
+ "undici": "8.5.0",
"yaml": "2.9.0"
},
"bin": {
@@ -6283,9 +5801,10 @@
"@types/ms": "2.1.0",
"@types/node": "24.12.4",
"@types/proper-lockfile": "4.1.4",
+ "@types/semver": "7.7.1",
"shx": "0.4.0",
"typescript": "5.9.3",
- "vitest": "3.2.4"
+ "vitest": "4.1.9"
},
"engines": {
"node": ">=22.19.0"
@@ -6296,32 +5815,32 @@
},
"packages/coding-agent/examples/extensions/custom-provider-anthropic": {
"name": "pi-extension-custom-provider-anthropic",
- "version": "0.79.1",
+ "version": "0.79.10",
"dependencies": {
"@anthropic-ai/sdk": "0.52.0"
}
},
"packages/coding-agent/examples/extensions/custom-provider-gitlab-duo": {
"name": "pi-extension-custom-provider-gitlab-duo",
- "version": "0.79.1"
+ "version": "0.79.10"
},
"packages/coding-agent/examples/extensions/gondolin": {
"name": "pi-extension-gondolin",
- "version": "0.79.1",
+ "version": "0.79.10",
"dependencies": {
"@earendil-works/gondolin": "0.12.0"
}
},
"packages/coding-agent/examples/extensions/sandbox": {
"name": "pi-extension-sandbox",
- "version": "1.9.1",
+ "version": "1.9.10",
"dependencies": {
"@anthropic-ai/sandbox-runtime": "0.0.26"
}
},
"packages/coding-agent/examples/extensions/with-deps": {
"name": "pi-extension-with-deps",
- "version": "0.79.1",
+ "version": "0.79.10",
"dependencies": {
"ms": "2.1.3"
},
@@ -6348,6 +5867,149 @@
"undici-types": "~7.16.0"
}
},
+ "packages/coding-agent/node_modules/@vitest/expect": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz",
+ "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.1.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.1.9",
+ "@vitest/utils": "4.1.9",
+ "chai": "^6.2.2",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "packages/coding-agent/node_modules/@vitest/pretty-format": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz",
+ "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "packages/coding-agent/node_modules/@vitest/runner": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz",
+ "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.1.9",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "packages/coding-agent/node_modules/@vitest/snapshot": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz",
+ "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.9",
+ "@vitest/utils": "4.1.9",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "packages/coding-agent/node_modules/@vitest/spy": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz",
+ "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "packages/coding-agent/node_modules/@vitest/utils": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz",
+ "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.9",
+ "convert-source-map": "^2.0.0",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "packages/coding-agent/node_modules/chai": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "packages/coding-agent/node_modules/es-module-lexer": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
+ "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "packages/coding-agent/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "packages/coding-agent/node_modules/std-env": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
+ "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "packages/coding-agent/node_modules/tinyexec": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
+ "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "packages/coding-agent/node_modules/tinyrainbow": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
+ "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"packages/coding-agent/node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
@@ -6355,13 +6017,130 @@
"dev": true,
"license": "MIT"
},
+ "packages/coding-agent/node_modules/vitest": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz",
+ "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "4.1.9",
+ "@vitest/mocker": "4.1.9",
+ "@vitest/pretty-format": "4.1.9",
+ "@vitest/runner": "4.1.9",
+ "@vitest/snapshot": "4.1.9",
+ "@vitest/spy": "4.1.9",
+ "@vitest/utils": "4.1.9",
+ "es-module-lexer": "^2.0.0",
+ "expect-type": "^1.3.0",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.1",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^4.0.0-rc.1",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^1.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.1.0",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@opentelemetry/api": "^1.9.0",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.1.9",
+ "@vitest/browser-preview": "4.1.9",
+ "@vitest/browser-webdriverio": "4.1.9",
+ "@vitest/coverage-istanbul": "4.1.9",
+ "@vitest/coverage-v8": "4.1.9",
+ "@vitest/ui": "4.1.9",
+ "happy-dom": "*",
+ "jsdom": "*",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/coverage-istanbul": {
+ "optional": true
+ },
+ "@vitest/coverage-v8": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ },
+ "vite": {
+ "optional": false
+ }
+ }
+ },
+ "packages/coding-agent/node_modules/vitest/node_modules/@vitest/mocker": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz",
+ "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "4.1.9",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
"packages/tui": {
"name": "@earendil-works/pi-tui",
- "version": "0.79.1",
+ "version": "0.79.10",
"license": "MIT",
"dependencies": {
"get-east-asian-width": "1.6.0",
- "marked": "15.0.12"
+ "marked": "18.0.5"
},
"devDependencies": {
"@xterm/headless": "5.5.0",
diff --git a/package.json b/package.json
index c88d7123..0deabeca 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,7 @@
"@biomejs/biome": "2.3.5",
"@types/node": "22.19.19",
"@typescript/native-preview": "7.0.0-dev.20260120.1",
- "esbuild": "0.28.0",
+ "esbuild": "0.28.1",
"husky": "9.1.7",
"jiti": "2.7.0",
"shx": "0.4.0",
diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md
index 8aed7436..a48ede06 100644
--- a/packages/agent/CHANGELOG.md
+++ b/packages/agent/CHANGELOG.md
@@ -8,6 +8,36 @@
- `compact()`, `generateSummary()`, and `generateBranchSummary()` take a `Models` parameter and no longer accept explicit `apiKey`/`headers`.
- `StreamFn` is defined structurally (`(model, context, options?) => AssistantMessageEventStream | Promise<...>`); `Models.streamSimple` satisfies it.
+## [0.79.10] - 2026-06-22
+
+## [0.79.9] - 2026-06-20
+
+### Fixed
+
+- Fixed Node execution environment commands through legacy WSL `bash.exe` to pass scripts over stdin so shell variables expand in the target bash ([#5893](https://github.com/earendil-works/pi/issues/5893)).
+
+## [0.79.8] - 2026-06-19
+
+### Added
+
+- Added `@earendil-works/pi-agent-core/base` for bundlers that want to pair the agent core with selective `@earendil-works/pi-ai/base` provider registration ([#5348](https://github.com/earendil-works/pi/pull/5348) by [@FredKSchott](https://github.com/FredKSchott)).
+
+## [0.79.7] - 2026-06-18
+
+## [0.79.6] - 2026-06-16
+
+## [0.79.5] - 2026-06-16
+
+## [0.79.4] - 2026-06-15
+
+## [0.79.3] - 2026-06-13
+
+## [0.79.2] - 2026-06-12
+
+### Fixed
+
+- Fixed late tool progress callbacks after tool settlement to be ignored instead of emitting stale `tool_execution_update` events ([#5573](https://github.com/earendil-works/pi/issues/5573)).
+
## [0.79.1] - 2026-06-09
## [0.79.0] - 2026-06-08
diff --git a/packages/agent/package.json b/packages/agent/package.json
index afd0368c..9322d7c6 100644
--- a/packages/agent/package.json
+++ b/packages/agent/package.json
@@ -1,6 +1,6 @@
{
"name": "@earendil-works/pi-agent-core",
- "version": "0.79.1",
+ "version": "0.79.10",
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
"type": "module",
"main": "./dist/index.js",
@@ -29,7 +29,7 @@
"prepublishOnly": "npm run clean && npm run build"
},
"dependencies": {
- "@earendil-works/pi-ai": "^0.79.1",
+ "@earendil-works/pi-ai": "^0.79.10",
"ignore": "7.0.5",
"typebox": "1.1.38",
"yaml": "2.9.0"
@@ -53,8 +53,8 @@
},
"devDependencies": {
"@types/node": "24.12.4",
- "@vitest/coverage-v8": "3.2.4",
+ "@vitest/coverage-v8": "4.1.9",
"typescript": "5.9.3",
- "vitest": "3.2.4"
+ "vitest": "4.1.9"
}
}
diff --git a/packages/agent/src/agent-loop.ts b/packages/agent/src/agent-loop.ts
index a3d270df..d93458d0 100644
--- a/packages/agent/src/agent-loop.ts
+++ b/packages/agent/src/agent-loop.ts
@@ -631,6 +631,7 @@ async function executePreparedToolCall(
emit: AgentEventSink,
): Promise {
const updateEvents: Promise[] = [];
+ let acceptingUpdates = true;
try {
const result = await prepared.tool.execute(
@@ -638,6 +639,7 @@ async function executePreparedToolCall(
prepared.args as never,
signal,
(partialResult) => {
+ if (!acceptingUpdates) return;
updateEvents.push(
Promise.resolve(
emit({
@@ -651,14 +653,18 @@ async function executePreparedToolCall(
);
},
);
+ acceptingUpdates = false;
await Promise.all(updateEvents);
return { result, isError: false };
} catch (error) {
+ acceptingUpdates = false;
await Promise.all(updateEvents);
return {
result: createErrorToolResult(error instanceof Error ? error.message : String(error)),
isError: true,
};
+ } finally {
+ acceptingUpdates = false;
}
}
diff --git a/packages/agent/src/harness/env/nodejs.ts b/packages/agent/src/harness/env/nodejs.ts
index e56e7aeb..3d929c82 100644
--- a/packages/agent/src/harness/env/nodejs.ts
+++ b/packages/agent/src/harness/env/nodejs.ts
@@ -144,12 +144,25 @@ async function findBashOnPath(): Promise {
return firstMatch && (await pathExists(firstMatch)) ? firstMatch : null;
}
-async function getShellConfig(
- customShellPath?: string,
-): Promise> {
+interface ShellConfig {
+ shell: string;
+ args: string[];
+ commandTransport?: "argv" | "stdin";
+}
+
+function isLegacyWslBashPath(path: string): boolean {
+ const normalized = path.replace(/\//g, "\\").toLowerCase();
+ return /^[a-z]:\\windows\\(?:system32|sysnative)\\bash\.exe$/.test(normalized);
+}
+
+function getBashShellConfig(shell: string): ShellConfig {
+ return isLegacyWslBashPath(shell) ? { shell, args: ["-s"], commandTransport: "stdin" } : { shell, args: ["-c"] };
+}
+
+async function getShellConfig(customShellPath?: string): Promise> {
if (customShellPath) {
if (await pathExists(customShellPath)) {
- return ok({ shell: customShellPath, args: ["-c"] });
+ return ok(getBashShellConfig(customShellPath));
}
return err(new ExecutionError("shell_unavailable", `Custom shell path not found: ${customShellPath}`));
}
@@ -161,22 +174,22 @@ async function getShellConfig(
if (programFilesX86) candidates.push(`${programFilesX86}\\Git\\bin\\bash.exe`);
for (const candidate of candidates) {
if (await pathExists(candidate)) {
- return ok({ shell: candidate, args: ["-c"] });
+ return ok(getBashShellConfig(candidate));
}
}
const bashOnPath = await findBashOnPath();
if (bashOnPath) {
- return ok({ shell: bashOnPath, args: ["-c"] });
+ return ok(getBashShellConfig(bashOnPath));
}
return err(new ExecutionError("shell_unavailable", "No bash shell found"));
}
if (await pathExists("/bin/bash")) {
- return ok({ shell: "/bin/bash", args: ["-c"] });
+ return ok(getBashShellConfig("/bin/bash"));
}
const bashOnPath = await findBashOnPath();
if (bashOnPath) {
- return ok({ shell: bashOnPath, args: ["-c"] });
+ return ok(getBashShellConfig(bashOnPath));
}
return ok({ shell: "sh", args: ["-c"] });
}
@@ -274,13 +287,22 @@ export class NodeExecutionEnv implements ExecutionEnv {
};
try {
- child = spawn(shellConfig.value.shell, [...shellConfig.value.args, command], {
- cwd,
- detached: process.platform !== "win32",
- env: getShellEnv(this.shellEnv, options?.env),
- stdio: ["ignore", "pipe", "pipe"],
- windowsHide: true,
- });
+ const commandFromStdin = shellConfig.value.commandTransport === "stdin";
+ child = spawn(
+ shellConfig.value.shell,
+ commandFromStdin ? shellConfig.value.args : [...shellConfig.value.args, command],
+ {
+ cwd,
+ detached: process.platform !== "win32",
+ env: getShellEnv(this.shellEnv, options?.env),
+ stdio: [commandFromStdin ? "pipe" : "ignore", "pipe", "pipe"],
+ windowsHide: true,
+ },
+ );
+ if (commandFromStdin) {
+ child.stdin?.on("error", () => {});
+ child.stdin?.end(command);
+ }
} catch (error) {
const cause = toError(error);
settle(err(new ExecutionError("spawn_error", cause.message, cause)));
diff --git a/packages/agent/src/types.ts b/packages/agent/src/types.ts
index fb6f0d8a..abfa3de6 100644
--- a/packages/agent/src/types.ts
+++ b/packages/agent/src/types.ts
@@ -359,7 +359,12 @@ export interface AgentToolResult {
terminate?: boolean;
}
-/** Callback used by tools to stream partial execution updates. */
+/**
+ * Callback used by tools to stream partial execution updates.
+ *
+ * The callback is scoped to the current `execute()` invocation. Calls made after
+ * the tool promise settles are ignored.
+ */
export type AgentToolUpdateCallback = (partialResult: AgentToolResult) => void;
/** Tool definition used by the agent runtime. */
diff --git a/packages/agent/test/agent.test.ts b/packages/agent/test/agent.test.ts
index 6fa00dd8..5fa27c5a 100644
--- a/packages/agent/test/agent.test.ts
+++ b/packages/agent/test/agent.test.ts
@@ -1,6 +1,7 @@
import { type AssistantMessage, type AssistantMessageEvent, EventStream, getModel } from "@earendil-works/pi-ai/compat";
+import { Type } from "typebox";
import { describe, expect, it } from "vitest";
-import { Agent } from "../src/index.ts";
+import { Agent, type AgentEvent, type AgentTool, type AgentToolUpdateCallback } from "../src/index.ts";
// Mock stream that mimics AssistantMessageEventStream
class MockAssistantStream extends EventStream {
@@ -36,6 +37,28 @@ function createAssistantMessage(text: string): AssistantMessage {
};
}
+type ToolCallContent = Extract;
+
+function createAssistantToolUseMessage(content: ToolCallContent[]): AssistantMessage {
+ return {
+ role: "assistant",
+ content,
+ api: "openai-responses",
+ provider: "openai",
+ model: "mock",
+ usage: {
+ input: 0,
+ output: 0,
+ cacheRead: 0,
+ cacheWrite: 0,
+ totalTokens: 0,
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
+ },
+ stopReason: "toolUse",
+ timestamp: Date.now(),
+ };
+}
+
function createDeferred(): {
promise: Promise;
resolve: () => void;
@@ -242,6 +265,147 @@ describe("Agent", () => {
expect(receivedSignal?.aborted).toBe(true);
});
+ it("should ignore tool updates after the tool execution settles", async () => {
+ const toolSchema = Type.Object({});
+ let delayedUpdate: AgentToolUpdateCallback<{ status: string }> | undefined;
+ const events: AgentEvent[] = [];
+ const unhandledRejections: unknown[] = [];
+ const onUnhandledRejection = (error: unknown) => {
+ unhandledRejections.push(error);
+ };
+ const tool: AgentTool = {
+ name: "delayed_tool",
+ label: "Delayed Tool",
+ description: "Captures progress callbacks",
+ parameters: toolSchema,
+ async execute(_toolCallId, _params, _signal, onUpdate) {
+ delayedUpdate = onUpdate;
+ onUpdate?.({
+ content: [{ type: "text", text: "running" }],
+ details: { status: "running" },
+ });
+ return {
+ content: [{ type: "text", text: "ok" }],
+ details: { status: "done" },
+ terminate: true,
+ };
+ },
+ };
+ const agent = new Agent({
+ initialState: { tools: [tool] },
+ streamFn: () => {
+ const stream = new MockAssistantStream();
+ queueMicrotask(() => {
+ stream.push({
+ type: "done",
+ reason: "toolUse",
+ message: createAssistantToolUseMessage([
+ { type: "toolCall", id: "call-1", name: "delayed_tool", arguments: {} },
+ ]),
+ });
+ });
+ return stream;
+ },
+ });
+ agent.subscribe((event) => {
+ events.push(event);
+ });
+
+ process.on("unhandledRejection", onUnhandledRejection);
+ try {
+ await agent.prompt("run tool");
+ const eventCountAfterPrompt = events.length;
+
+ delayedUpdate?.({
+ content: [{ type: "text", text: "late" }],
+ details: { status: "late" },
+ });
+ await new Promise((resolve) => setTimeout(resolve, 0));
+
+ expect(events.filter((event) => event.type === "tool_execution_update")).toHaveLength(1);
+ expect(events).toHaveLength(eventCountAfterPrompt);
+ expect(unhandledRejections).toEqual([]);
+ } finally {
+ process.off("unhandledRejection", onUnhandledRejection);
+ }
+ });
+
+ it("should ignore a settled parallel tool update while another tool is still running", async () => {
+ const toolSchema = Type.Object({});
+ const slowStarted = createDeferred();
+ const settledToolEnded = createDeferred();
+ const releaseSlow = createDeferred();
+ let settledToolUpdate: AgentToolUpdateCallback<{ status: string }> | undefined;
+ const events: AgentEvent[] = [];
+ const settledTool: AgentTool = {
+ name: "settled_tool",
+ label: "Settled Tool",
+ description: "Captures progress callbacks",
+ parameters: toolSchema,
+ async execute(_toolCallId, _params, _signal, onUpdate) {
+ settledToolUpdate = onUpdate;
+ return {
+ content: [{ type: "text", text: "done" }],
+ details: { status: "done" },
+ terminate: true,
+ };
+ },
+ };
+ const slowTool: AgentTool = {
+ name: "slow_tool",
+ label: "Slow Tool",
+ description: "Keeps the agent run active",
+ parameters: toolSchema,
+ async execute() {
+ slowStarted.resolve();
+ await releaseSlow.promise;
+ return {
+ content: [{ type: "text", text: "done" }],
+ details: { status: "done" },
+ terminate: true,
+ };
+ },
+ };
+ const agent = new Agent({
+ initialState: { tools: [settledTool, slowTool] },
+ streamFn: () => {
+ const stream = new MockAssistantStream();
+ queueMicrotask(() => {
+ stream.push({
+ type: "done",
+ reason: "toolUse",
+ message: createAssistantToolUseMessage([
+ { type: "toolCall", id: "call-1", name: "settled_tool", arguments: {} },
+ { type: "toolCall", id: "call-2", name: "slow_tool", arguments: {} },
+ ]),
+ });
+ });
+ return stream;
+ },
+ });
+ agent.subscribe((event) => {
+ events.push(event);
+ if (event.type === "tool_execution_end" && event.toolCallId === "call-1") {
+ settledToolEnded.resolve();
+ }
+ });
+
+ const promptPromise = agent.prompt("run tools");
+ await Promise.all([slowStarted.promise, settledToolEnded.promise]);
+ const eventCountBeforeLateUpdate = events.length;
+
+ settledToolUpdate?.({
+ content: [{ type: "text", text: "late" }],
+ details: { status: "late" },
+ });
+ await new Promise((resolve) => setTimeout(resolve, 0));
+ expect(events).toHaveLength(eventCountBeforeLateUpdate);
+
+ releaseSlow.resolve();
+ await promptPromise;
+ expect(events.filter((event) => event.type === "tool_execution_update")).toHaveLength(0);
+ });
+
it("should update state with mutators", () => {
const agent = new Agent();
diff --git a/packages/agent/test/harness/nodejs-env.test.ts b/packages/agent/test/harness/nodejs-env.test.ts
index 758d5f59..d2d33a6f 100644
--- a/packages/agent/test/harness/nodejs-env.test.ts
+++ b/packages/agent/test/harness/nodejs-env.test.ts
@@ -1,5 +1,5 @@
import { access, chmod, realpath, symlink } from "node:fs/promises";
-import { join } from "node:path";
+import { delimiter, join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.ts";
import { FileError, getOrThrow } from "../../src/harness/types.ts";
@@ -201,6 +201,39 @@ describe("NodeExecutionEnv", () => {
expect(result).toEqual({ stdout: `${await realpath(root)}:ok`, stderr: "", exitCode: 0 });
});
+ it("uses stdin command transport for legacy WSL bash paths", async () => {
+ if (process.platform === "win32") return;
+ const root = createTempDir();
+ const shellPath = "C:\\Windows\\System32\\bash.exe";
+ const env = new NodeExecutionEnv({ cwd: root });
+ getOrThrow(await env.writeFile(shellPath, '#!/bin/sh\nprintf \'args:%s\\n\' "$*" >&2\nexec /bin/bash "$@"\n'));
+ await chmod(join(root, shellPath), 0o755);
+
+ const originalCwd = process.cwd();
+ const originalPath = process.env.PATH;
+ const platformDescriptor = Object.getOwnPropertyDescriptor(process, "platform");
+ try {
+ process.chdir(root);
+ process.env.PATH = `${root}${delimiter}${originalPath ?? ""}`;
+ Object.defineProperty(process, "platform", {
+ configurable: true,
+ value: "win32",
+ });
+
+ const wslEnv = new NodeExecutionEnv({ cwd: root, shellPath });
+ const nameExpansion = "$" + "{name}";
+ const result = getOrThrow(await wslEnv.exec(`name='World'; echo "Hello, ${nameExpansion}!"`));
+
+ expect(result).toEqual({ stdout: "Hello, World!\n", stderr: "args:-s\n", exitCode: 0 });
+ } finally {
+ process.chdir(originalCwd);
+ process.env.PATH = originalPath;
+ if (platformDescriptor) {
+ Object.defineProperty(process, "platform", platformDescriptor);
+ }
+ }
+ });
+
it("streams stdout and stderr chunks", async () => {
const root = createTempDir();
const env = new NodeExecutionEnv({ cwd: root });
diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md
index c450e028..0636c010 100644
--- a/packages/ai/CHANGELOG.md
+++ b/packages/ai/CHANGELOG.md
@@ -23,6 +23,86 @@
- Fixed Claude Fable 5 thinking-off requests to omit Anthropic's unsupported `thinking.type: "disabled"` payload ([#5567](https://github.com/earendil-works/pi/pull/5567) by [@tmustier](https://github.com/tmustier)).
+## [0.79.10] - 2026-06-22
+
+### Fixed
+
+- Fixed OpenAI-compatible streaming to preserve encrypted `reasoning_details` that arrive before matching tool call deltas ([#5114](https://github.com/earendil-works/pi/issues/5114)).
+
+## [0.79.9] - 2026-06-20
+
+### Added
+
+- Added configurable `chat-template` thinking support for OpenAI-compatible providers that use `chat_template_kwargs`, such as DeepSeek models behind vLLM ([#5673](https://github.com/earendil-works/pi/issues/5673)).
+
+### Fixed
+
+- Fixed Fireworks GLM-5.2 metadata to use the OpenAI-compatible Chat Completions endpoint with `reasoning_effort` support ([#5923](https://github.com/earendil-works/pi/issues/5923)).
+- Fixed OpenRouter GLM-5.2 metadata to expose `xhigh` reasoning and send OpenRouter's native `xhigh` effort ([#5770](https://github.com/earendil-works/pi/issues/5770)).
+- Fixed GitHub Copilot OAuth model availability to use the authenticated account's model picker catalog ([#5897](https://github.com/earendil-works/pi/issues/5897)).
+
+## [0.79.8] - 2026-06-19
+
+### Added
+
+- Added `@earendil-works/pi-ai/base` and direct provider registration exports for bundlers that want selective provider transports without root built-in registration ([#5348](https://github.com/earendil-works/pi/pull/5348) by [@FredKSchott](https://github.com/FredKSchott)).
+- Added prompt caching for Mistral requests using the pi session ID as `prompt_cache_key`, including cached-token usage and cost accounting ([#5854](https://github.com/earendil-works/pi/issues/5854)).
+- Added the OpenRouter Fusion alias as `openrouter/fusion` ([#5866](https://github.com/earendil-works/pi/pull/5866) by [@dannote](https://github.com/dannote)).
+
+## [0.79.7] - 2026-06-18
+
+### Added
+
+- Added GLM-5.2 model to the OpenCode Go subscription model catalog ([#5860](https://github.com/earendil-works/pi/issues/5860)).
+
+## [0.79.6] - 2026-06-16
+
+### Fixed
+
+- Fixed OpenCode Go DeepSeek V4 thinking-off requests to send the provider's `thinking: { type: "disabled" }` compatibility parameter.
+
+## [0.79.5] - 2026-06-16
+
+### Added
+
+- Added provider-scoped `StreamOptions.env` overrides for provider configuration, including Cloudflare endpoint placeholders, Azure OpenAI, Google Vertex, Amazon Bedrock, cache retention, and proxy environment lookups ([#5728](https://github.com/earendil-works/pi/issues/5728)).
+
+### Fixed
+
+- Fixed OpenAI Responses streaming to tolerate null message content from OpenAI-compatible servers before tool calls ([#5819](https://github.com/earendil-works/pi/issues/5819)).
+- Fixed OpenCode DeepSeek V4 thinking requests to avoid sending both `thinking` and `reasoning_effort` ([#5818](https://github.com/earendil-works/pi/issues/5818)).
+- Fixed Z.AI GLM-5.2 thinking requests to send `reasoning_effort` with the provider's `high`/`max` effort mapping ([#5770](https://github.com/earendil-works/pi/issues/5770)).
+- Fixed Google and `google-vertex` Gemini model metadata to map `latest` aliases to the current models, add Gemini 3.5 Flash for Vertex, correct Gemini 2.5 Flash Vertex cache pricing, and remove shut-down Vertex preview models ([#5761](https://github.com/earendil-works/pi/issues/5761)).
+- Fixed Moonshot AI China model metadata to include Kimi K2.7 Code, and omitted unsupported thinking-off payloads for Kimi K2.7 Code models ([#5760](https://github.com/earendil-works/pi/issues/5760)).
+
+## [0.79.4] - 2026-06-15
+
+### Fixed
+
+- Fixed Anthropic 1-hour prompt-cache write cost accounting to price 1-hour cache writes at 2x input instead of the 5-minute cache-write rate ([#5738](https://github.com/earendil-works/pi/pull/5738) by [@theBucky](https://github.com/theBucky)).
+- Fixed GitHub Copilot Claude adaptive-thinking effort metadata to match manually checked Copilot model capabilities ([#4637](https://github.com/earendil-works/pi/issues/4637)).
+- Fixed OpenCode/OpenCode Go completion models that reject `prompt_cache_retention` to omit long-retention cache fields when `cacheRetention` is `long` ([#5702](https://github.com/earendil-works/pi/issues/5702)).
+
+## [0.79.3] - 2026-06-13
+
+### Fixed
+
+- Restored OpenAI GPT-5.4/GPT-5.5 and OpenAI Codex GPT-5.4/GPT-5.4 mini/GPT-5.5 context window metadata to the observed 272k-token Codex backend limit, avoiding a billing hazard from sending prompts above Codex's accepted limit (reported by [@trethore](https://github.com/trethore)).
+
+## [0.79.2] - 2026-06-12
+
+### Added
+
+- Added AWS data retention documentation links to Amazon Bedrock unsupported data retention mode validation errors ([#5561](https://github.com/earendil-works/pi/pull/5561) by [@unexge](https://github.com/unexge)).
+
+### Fixed
+
+- Fixed OpenAI-compatible context overflow detection for parenthesized `maximum context length (N)` errors ([#5677](https://github.com/earendil-works/pi/issues/5677)).
+- Fixed OpenAI GPT-5.4/GPT-5.5 and OpenAI Codex GPT-5.4/GPT-5.4 mini/GPT-5.5 context window metadata to match current OpenAI limits ([#5644](https://github.com/earendil-works/pi/issues/5644)).
+- Increased the OpenAI Codex Responses SSE response-header timeout to 20 seconds to reduce false-positive stalls while retaining the bounded wait introduced for zero-event hangs ([#4945](https://github.com/earendil-works/pi/issues/4945)).
+- Fixed Anthropic refusal stops to preserve provider `stop_details` explanations in error messages ([#5666](https://github.com/earendil-works/pi/pull/5666) by [@rwachtler](https://github.com/rwachtler)).
+- Fixed Claude Fable 5 thinking-off requests to omit Anthropic's unsupported `thinking.type: "disabled"` payload ([#5567](https://github.com/earendil-works/pi/pull/5567) by [@tmustier](https://github.com/tmustier)).
+
## [0.79.1] - 2026-06-09
### Added
diff --git a/packages/ai/README.md b/packages/ai/README.md
index 735e6c18..9c114191 100644
--- a/packages/ai/README.md
+++ b/packages/ai/README.md
@@ -1055,7 +1055,8 @@ interface OpenAICompletionsCompat {
requiresAssistantAfterToolResult?: boolean; // Whether tool results must be followed by an assistant message (default: false)
requiresThinkingAsText?: boolean; // Whether thinking blocks must be converted to text (default: false)
requiresReasoningContentOnAssistantMessages?: boolean; // Whether all replayed assistant messages must include empty reasoning_content when reasoning is enabled (default: auto-detected for DeepSeek)
- thinkingFormat?: 'openai' | 'openrouter' | 'deepseek' | 'together' | 'zai' | 'qwen' | 'qwen-chat-template' | 'string-thinking' | 'ant-ling'; // Format for reasoning param: 'openai' uses reasoning_effort, 'openrouter' uses reasoning: { effort }, 'deepseek' uses thinking: { type } plus reasoning_effort when supported, 'together' uses reasoning: { enabled } plus reasoning_effort when supported, 'zai' uses enable_thinking, 'qwen' uses enable_thinking, 'qwen-chat-template' uses chat_template_kwargs.enable_thinking, 'string-thinking' uses top-level thinking, 'ant-ling' uses reasoning: { effort } only for mapped efforts (default: openai)
+ thinkingFormat?: 'openai' | 'openrouter' | 'deepseek' | 'together' | 'zai' | 'qwen' | 'chat-template' | 'qwen-chat-template' | 'string-thinking' | 'ant-ling'; // Format for reasoning param: 'openai' uses reasoning_effort, 'openrouter' uses reasoning: { effort }, 'deepseek' uses thinking: { type } plus reasoning_effort when supported, 'together' uses reasoning: { enabled } plus reasoning_effort when supported, 'zai' uses thinking: { type }, 'qwen' uses enable_thinking, 'chat-template' uses configurable chat_template_kwargs, 'qwen-chat-template' uses chat_template_kwargs.enable_thinking and preserve_thinking, 'string-thinking' uses top-level thinking, 'ant-ling' uses reasoning: { effort } only for mapped efforts (default: openai)
+ chatTemplateKwargs?: Record; // chat_template_kwargs values; use $var for pi-controlled thinking values
cacheControlFormat?: 'anthropic'; // Anthropic-style cache_control on system prompt, last tool, and last user/assistant text content
openRouterRouting?: OpenRouterRouting; // OpenRouter routing preferences (default: {})
vercelGatewayRouting?: VercelGatewayRouting; // Vercel AI Gateway routing preferences (default: {})
@@ -1265,6 +1266,25 @@ Browser compatibility notes:
- OAuth login flows are Node-only. They are lazy-loaded behind bundler-opaque imports, so registering an OAuth-capable provider does not pull Node-only code into a browser bundle — only actually logging in would.
- Use a server-side proxy or backend service if you need Bedrock or OAuth-based auth from a web app.
+### Provider-Scoped Environment Overrides
+
+Pass `env` in stream options to scope provider configuration to a request. Values in `env` are used before process environment variables for provider auth and configuration such as Cloudflare account IDs, Azure OpenAI settings, Vertex project/location, Bedrock settings, `PI_CACHE_RETENTION`, and `HTTP_PROXY`/`HTTPS_PROXY`.
+
+```typescript
+const models = builtinModels();
+const model = models.getModel('cloudflare-ai-gateway', 'workers-ai/@cf/moonshotai/kimi-k2.6')!;
+
+const response = await models.complete(model, context, {
+ env: {
+ CLOUDFLARE_API_KEY: '...',
+ CLOUDFLARE_ACCOUNT_ID: 'account-id',
+ CLOUDFLARE_GATEWAY_ID: 'gateway-id'
+ }
+});
+```
+
+Use this when one process needs different provider settings per request, or when ambient environment variables should not leak into a provider call.
+
## OAuth Providers
Several providers support OAuth authentication instead of static API keys:
diff --git a/packages/ai/package.json b/packages/ai/package.json
index b4112894..41392a2b 100644
--- a/packages/ai/package.json
+++ b/packages/ai/package.json
@@ -1,6 +1,6 @@
{
"name": "@earendil-works/pi-ai",
- "version": "0.79.1",
+ "version": "0.79.10",
"description": "Unified LLM API with automatic model discovery and provider configuration",
"type": "module",
"main": "./dist/index.js",
@@ -86,9 +86,10 @@
"dependencies": {
"@anthropic-ai/sdk": "0.91.1",
"@aws-sdk/client-bedrock-runtime": "3.1048.0",
- "@smithy/node-http-handler": "4.7.3",
"@google/genai": "1.52.0",
- "@mistralai/mistralai": "2.2.1",
+ "@mistralai/mistralai": "2.2.6",
+ "@opentelemetry/api": "1.9.0",
+ "@smithy/node-http-handler": "4.7.3",
"http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.6",
"openai": "6.26.0",
@@ -118,6 +119,6 @@
"devDependencies": {
"@types/node": "24.12.4",
"canvas": "3.2.3",
- "vitest": "3.2.4"
+ "vitest": "4.1.9"
}
}
diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts
index 51285220..eaf1234f 100644
--- a/packages/ai/scripts/generate-models.ts
+++ b/packages/ai/scripts/generate-models.ts
@@ -68,6 +68,8 @@ const KIMI_STATIC_HEADERS = {
"User-Agent": "KimiCLI/1.5",
} as const;
+const MOONSHOT_CN_MIRRORED_MODEL_IDS = new Set(["kimi-k2.7-code", "kimi-k2.7-code-highspeed"]);
+
const TOGETHER_BASE_URL = "https://api.together.ai/v1";
const TOGETHER_BASE_COMPAT: OpenAICompletionsCompat = {
supportsStore: false,
@@ -121,6 +123,7 @@ const TOGETHER_TOGGLE_REASONING_LEVEL_MAP = {
const AI_GATEWAY_MODELS_URL = "https://ai-gateway.vercel.sh/v1";
const AI_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh";
+const VERTEX_BASE_URL = "https://{location}-aiplatform.googleapis.com";
const NVIDIA_BASE_URL = "https://integrate.api.nvidia.com/v1";
const NVIDIA_HEADERS = {
"NVCF-POLL-SECONDS": "3600",
@@ -154,6 +157,13 @@ const NVIDIA_NIM_UNSUPPORTED_MODELS = new Set([
"upstage/solar-10.7b-instruct",
]);
const ZAI_TOOL_STREAM_UNSUPPORTED_MODELS = new Set(["glm-4.5", "glm-4.5-air", "glm-4.5-flash", "glm-4.5v"]);
+const ZAI_GLM52_THINKING_LEVEL_MAP = {
+ minimal: null,
+ low: "high",
+ medium: "high",
+ high: "high",
+ xhigh: "max",
+} as const;
const EAGER_TOOL_INPUT_STREAMING_UNSUPPORTED_ANTHROPIC_MODELS = new Set([
"github-copilot:claude-haiku-4.5",
"github-copilot:claude-sonnet-4",
@@ -187,6 +197,23 @@ const OPENAI_RESPONSES_NONE_REASONING_MODELS = new Set([
"gpt-5.5",
]);
+const OPENCODE_OPENAI_COMPLETIONS_LONG_CACHE_RETENTION_UNSUPPORTED_MODELS = new Set([
+ "opencode:deepseek-v4-flash",
+ "opencode:deepseek-v4-pro",
+ "opencode:kimi-k2.5",
+ "opencode:kimi-k2.6",
+ "opencode:minimax-m2.7",
+ "opencode-go:kimi-k2.6",
+]);
+
+// Checked manually against the authenticated GitHub Copilot /models endpoint on 2026-06-15.
+// Keep this to narrow corrections over models.dev metadata instead of snapshotting Copilot's catalog.
+const GITHUB_COPILOT_THINKING_LEVEL_OVERRIDES = {
+ "claude-opus-4.7": { minimal: "low" },
+ "claude-opus-4.8": { minimal: "low" },
+ "claude-sonnet-4.6": { minimal: "low", xhigh: "max" },
+} satisfies Record["thinkingLevelMap"]>>;
+
function mergeThinkingLevelMap(model: Model, map: NonNullable["thinkingLevelMap"]>): void {
model.thinkingLevelMap = { ...model.thinkingLevelMap, ...map };
}
@@ -251,7 +278,8 @@ function isGemini3ProModel(modelId: string): boolean {
}
function isGemini3FlashModel(modelId: string): boolean {
- return /gemini-3(?:\.\d+)?-flash/.test(modelId.toLowerCase());
+ const id = modelId.toLowerCase();
+ return /gemini-3(?:\.\d+)?-flash/.test(id) || id === "gemini-flash-latest" || id === "gemini-flash-lite-latest";
}
function isGemma4Model(modelId: string): boolean {
@@ -330,6 +358,15 @@ function applyThinkingLevelMetadata(model: Model): void {
if (model.provider === "openai-codex" && supportsOpenAiXhigh(model.id)) {
mergeThinkingLevelMap(model, { minimal: "low" });
}
+ if (
+ (model.provider === "moonshotai" || model.provider === "moonshotai-cn") &&
+ (model.id === "kimi-k2.7-code" || model.id === "kimi-k2.7-code-highspeed")
+ ) {
+ // Kimi K2.7 Code is always-thinking. Official docs say
+ // `thinking: { type: "disabled" }` is rejected, and callers can omit
+ // the thinking parameter to use the enabled default.
+ mergeThinkingLevelMap(model, { off: null });
+ }
if (model.provider === "openrouter" && model.id.startsWith("inception/mercury-2")) {
// Mercury 2 in instant mode (reasoning_effort: "none") disables tool calling.
// Mark "off" unsupported so the openai-completions provider omits the reasoning param
@@ -337,6 +374,12 @@ function applyThinkingLevelMetadata(model: Model): void {
// Pi's low/medium/high pass through verbatim; OpenRouter normalizes to Mercury's vocabulary.
mergeThinkingLevelMap(model, { off: null });
}
+ if (model.provider === "openrouter" && model.id === "z-ai/glm-5.2") {
+ mergeThinkingLevelMap(model, { xhigh: "xhigh" });
+ }
+ if (model.provider === "fireworks" && model.id === "accounts/fireworks/models/glm-5p2") {
+ mergeThinkingLevelMap(model, { off: "none", minimal: null, low: "high", medium: "high", xhigh: "max" });
+ }
if (model.provider === "opencode-go" && model.id === "kimi-k2.6") {
// OpenCode Go exposes Kimi K2.6 thinking as on/off, not distinct effort tiers.
mergeThinkingLevelMap(model, { minimal: null, low: null, medium: null });
@@ -349,6 +392,12 @@ function applyThinkingLevelMetadata(model: Model): void {
// Ring reasons by default. Only high/xhigh have documented explicit effort controls.
mergeThinkingLevelMap(model, ANT_LING_RING_THINKING_LEVEL_MAP);
}
+ if (model.provider === "github-copilot") {
+ const override = GITHUB_COPILOT_THINKING_LEVEL_OVERRIDES[model.id];
+ if (override) {
+ mergeThinkingLevelMap(model, override);
+ }
+ }
}
function getAnthropicMessagesCompat(provider: string, modelId: string): AnthropicMessagesCompat | undefined {
@@ -372,6 +421,10 @@ function normalizeNvidiaModelId(modelId: string): string {
return modelId.toLowerCase().replaceAll("_", ".");
}
+function roundCost(value: number): number {
+ return Number(value.toFixed(6));
+}
+
async function fetchNvidiaNimModelIds(): Promise> {
try {
console.log("Fetching models from NVIDIA NIM API...");
@@ -417,10 +470,10 @@ async function fetchOpenRouterModels(): Promise[]> {
}
// Convert pricing from $/token to $/million tokens
- const inputCost = parseFloat(model.pricing?.prompt || "0") * 1_000_000;
- const outputCost = parseFloat(model.pricing?.completion || "0") * 1_000_000;
- const cacheReadCost = parseFloat(model.pricing?.input_cache_read || "0") * 1_000_000;
- const cacheWriteCost = parseFloat(model.pricing?.input_cache_write || "0") * 1_000_000;
+ const inputCost = roundCost(parseFloat(model.pricing?.prompt || "0") * 1_000_000);
+ const outputCost = roundCost(parseFloat(model.pricing?.completion || "0") * 1_000_000);
+ const cacheReadCost = roundCost(parseFloat(model.pricing?.input_cache_read || "0") * 1_000_000);
+ const cacheWriteCost = roundCost(parseFloat(model.pricing?.input_cache_write || "0") * 1_000_000);
const normalizedModel: Model = {
id: modelKey,
@@ -476,10 +529,10 @@ async function fetchAiGatewayModels(): Promise[]> {
input.push("image");
}
- const inputCost = toNumber(model.pricing?.input) * 1_000_000;
- const outputCost = toNumber(model.pricing?.output) * 1_000_000;
- const cacheReadCost = toNumber(model.pricing?.input_cache_read) * 1_000_000;
- const cacheWriteCost = toNumber(model.pricing?.input_cache_write) * 1_000_000;
+ const inputCost = roundCost(toNumber(model.pricing?.input) * 1_000_000);
+ const outputCost = roundCost(toNumber(model.pricing?.output) * 1_000_000);
+ const cacheReadCost = roundCost(toNumber(model.pricing?.input_cache_read) * 1_000_000);
+ const cacheWriteCost = roundCost(toNumber(model.pricing?.input_cache_write) * 1_000_000);
models.push({
id: model.id,
@@ -586,6 +639,13 @@ async function loadModelsDevData(): Promise[]> {
for (const [modelId, model] of Object.entries(data.google.models)) {
const m = model as ModelsDevModel;
if (m.tool_call !== true) continue;
+ let source = m;
+ if (modelId === "gemini-flash-latest") {
+ source = (data.google.models["gemini-3.5-flash"] as ModelsDevModel | undefined) ?? m;
+ }
+ if (modelId === "gemini-flash-lite-latest") {
+ source = (data.google.models["gemini-3.1-flash-lite"] as ModelsDevModel | undefined) ?? m;
+ }
models.push({
id: modelId,
@@ -593,16 +653,57 @@ async function loadModelsDevData(): Promise[]> {
api: "google-generative-ai",
provider: "google",
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
- reasoning: m.reasoning === true,
- input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
+ reasoning: source.reasoning === true,
+ input: source.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
cost: {
- input: m.cost?.input || 0,
- output: m.cost?.output || 0,
- cacheRead: m.cost?.cache_read || 0,
- cacheWrite: m.cost?.cache_write || 0,
+ input: source.cost?.input || 0,
+ output: source.cost?.output || 0,
+ cacheRead: source.cost?.cache_read || 0,
+ cacheWrite: source.cost?.cache_write || 0,
},
- contextWindow: m.limit?.context || 4096,
- maxTokens: m.limit?.output || 4096,
+ contextWindow: source.limit?.context || 4096,
+ maxTokens: source.limit?.output || 4096,
+ });
+ }
+ }
+
+ // Process Google Vertex Gemini models. The google-vertex models.dev catalog also includes
+ // Claude, OpenAI, and other MaaS models that do not use the @google/genai Gemini streaming
+ // path implemented by our google-vertex provider.
+ if (data["google-vertex"]?.models) {
+ for (const [modelId, model] of Object.entries(data["google-vertex"].models)) {
+ const m = model as ModelsDevModel;
+ if (m.tool_call !== true) continue;
+ if (!modelId.startsWith("gemini-")) continue;
+ if (modelId === "gemini-3.1-flash-lite-preview") continue;
+ let source = m;
+ if (modelId === "gemini-flash-latest") {
+ source = (data["google-vertex"].models["gemini-3.5-flash"] as ModelsDevModel | undefined) ?? m;
+ }
+ if (modelId === "gemini-flash-lite-latest") {
+ source = (data["google-vertex"].models["gemini-3.1-flash-lite"] as ModelsDevModel | undefined) ?? m;
+ }
+
+ // models.dev reports Vertex cache_read/cache_write values for Gemini 2.5 Flash that
+ // do not match the official Gemini API standard pricing table. pi only accounts
+ // cachedContentTokenCount as cacheRead.
+ const cacheRead = modelId === "gemini-2.5-flash" ? 0.03 : source.cost?.cache_read || 0;
+ models.push({
+ id: modelId,
+ name: m.name || modelId,
+ api: "google-vertex",
+ provider: "google-vertex",
+ baseUrl: VERTEX_BASE_URL,
+ reasoning: source.reasoning === true,
+ input: source.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
+ cost: {
+ input: source.cost?.input || 0,
+ output: source.cost?.output || 0,
+ cacheRead,
+ cacheWrite: 0,
+ },
+ contextWindow: source.limit?.context || 4096,
+ maxTokens: source.limit?.output || 4096,
});
}
}
@@ -806,6 +907,8 @@ async function loadModelsDevData(): Promise[]> {
if (m.tool_call !== true) continue;
const supportsImage = m.modalities?.input?.includes("image");
+ const isGlm52 = modelId === "glm-5.2";
+
models.push({
id: modelId,
name: m.name || modelId,
@@ -813,6 +916,7 @@ async function loadModelsDevData(): Promise[]> {
provider,
baseUrl,
reasoning: m.reasoning === true,
+ ...(isGlm52 ? { thinkingLevelMap: ZAI_GLM52_THINKING_LEVEL_MAP } : {}),
input: supportsImage ? ["text", "image"] : ["text"],
cost: {
input: m.cost?.input || 0,
@@ -823,6 +927,7 @@ async function loadModelsDevData(): Promise[]> {
compat: {
supportsDeveloperRole: false,
thinkingFormat: "zai",
+ ...(isGlm52 ? { supportsReasoningEffort: true } : {}),
...(!ZAI_TOOL_STREAM_UNSUPPORTED_MODELS.has(modelId) ? { zaiToolStream: true } : {}),
},
contextWindow: m.limit?.context || 4096,
@@ -849,7 +954,7 @@ async function loadModelsDevData(): Promise[]> {
cost: {
input: m.cost?.input || 0,
output: m.cost?.output || 0,
- cacheRead: m.cost?.cache_read || 0,
+ cacheRead: m.cost?.cache_read ?? (m.cost?.input ? roundCost(m.cost.input * 0.1) : 0),
cacheWrite: m.cost?.cache_write || 0,
},
contextWindow: m.limit?.context || 4096,
@@ -1066,6 +1171,13 @@ async function loadModelsDevData(): Promise[]> {
if (api === "openai-completions") {
compat = { ...(compat ?? {}), maxTokensField: "max_tokens" };
+ if (
+ OPENCODE_OPENAI_COMPLETIONS_LONG_CACHE_RETENTION_UNSUPPORTED_MODELS.has(
+ `${variant.provider}:${modelId}`,
+ )
+ ) {
+ compat = { ...compat, supportsLongCacheRetention: false };
+ }
}
models.push({
@@ -1228,12 +1340,27 @@ async function loadModelsDevData(): Promise[]> {
supportsStrictMode: false,
thinkingFormat: "deepseek",
};
+ const getMoonshotProviderModels = (key: "moonshotai" | "moonshotai-cn"): Record => {
+ const providerModels = data[key]?.models as Record | undefined;
+ return providerModels ? { ...providerModels } : {};
+ };
+ const moonshotModels = {
+ moonshotai: getMoonshotProviderModels("moonshotai"),
+ "moonshotai-cn": getMoonshotProviderModels("moonshotai-cn"),
+ };
+
+ // models.dev can lag the CN catalog while the global Moonshot catalog already
+ // has the model. Mirror selected current model IDs into moonshotai-cn until
+ // upstream CN metadata catches up.
+ for (const modelId of MOONSHOT_CN_MIRRORED_MODEL_IDS) {
+ const model = moonshotModels.moonshotai[modelId];
+ if (model && !moonshotModels["moonshotai-cn"][modelId]) {
+ moonshotModels["moonshotai-cn"][modelId] = model;
+ }
+ }
for (const { key, provider, baseUrl } of moonshotVariants) {
- if (!data[key]?.models) continue;
-
- for (const [modelId, model] of Object.entries(data[key].models)) {
- const m = model as ModelsDevModel;
+ for (const [modelId, m] of Object.entries(moonshotModels[key])) {
if (m.tool_call !== true) continue;
models.push({
@@ -1390,7 +1517,11 @@ async function generateModels() {
candidate.cost.output = 1.9;
candidate.cost.cacheRead = 0.119;
}
-
+ if (candidate.provider === "fireworks" && candidate.id === "accounts/fireworks/models/glm-5p2") {
+ candidate.api = "openai-completions";
+ candidate.baseUrl = "https://api.fireworks.ai/inference/v1";
+ candidate.compat = { supportsStore: false, supportsDeveloperRole: false };
+ }
}
@@ -1731,9 +1862,10 @@ async function generateModels() {
for (const candidate of allModels) {
if (candidate.api === "openai-completions" && candidate.id.includes("deepseek-v4")) {
+ const preservesNativeReasoningEffort = candidate.provider === "openrouter" || candidate.provider === "opencode";
candidate.compat = {
...candidate.compat,
- ...(candidate.provider === "openrouter"
+ ...(preservesNativeReasoningEffort
? {
requiresReasoningContentOnAssistantMessages:
deepseekCompat.requiresReasoningContentOnAssistantMessages,
@@ -1908,166 +2040,31 @@ async function generateModels() {
});
}
- const VERTEX_BASE_URL = "https://{location}-aiplatform.googleapis.com";
- const vertexModels: Model<"google-vertex">[] = [
- {
- id: "gemini-3-pro-preview",
- name: "Gemini 3 Pro Preview (Vertex)",
- api: "google-vertex",
- provider: "google-vertex",
- baseUrl: VERTEX_BASE_URL,
+ // Add "fusion" alias for openrouter/fusion. OpenRouter exposes Fusion as a
+ // router alias/plugin entry point; its model metadata does not advertise
+ // tools, but the alias resolves to a concrete model that can invoke caller
+ // tools and has the openrouter:fusion server tool auto-injected.
+ if (!allModels.some(m => m.provider === "openrouter" && m.id === "openrouter/fusion")) {
+ allModels.push({
+ id: "openrouter/fusion",
+ name: "OpenRouter: Fusion",
+ api: "openai-completions",
+ provider: "openrouter",
+ baseUrl: "https://openrouter.ai/api/v1",
reasoning: true,
- input: ["text", "image"],
- cost: { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 },
+ input: ["text"],
+ cost: {
+ // we dont know about the costs because Fusion routes to multiple models
+ // and then charges you for the underlying used models
+ input: 0,
+ output: 0,
+ cacheRead: 0,
+ cacheWrite: 0,
+ },
contextWindow: 1000000,
- maxTokens: 64000,
- },
- {
- id: "gemini-3.1-pro-preview",
- name: "Gemini 3.1 Pro Preview (Vertex)",
- api: "google-vertex",
- provider: "google-vertex",
- baseUrl: VERTEX_BASE_URL,
- reasoning: true,
- input: ["text", "image"],
- cost: { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 },
- contextWindow: 1048576,
- maxTokens: 65536,
- },
- {
- id: "gemini-3.1-pro-preview-customtools",
- name: "Gemini 3.1 Pro Preview Custom Tools (Vertex)",
- api: "google-vertex",
- provider: "google-vertex",
- baseUrl: VERTEX_BASE_URL,
- reasoning: true,
- input: ["text", "image"],
- cost: { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 },
- contextWindow: 1048576,
- maxTokens: 65536,
- },
- {
- id: "gemini-3-flash-preview",
- name: "Gemini 3 Flash Preview (Vertex)",
- api: "google-vertex",
- provider: "google-vertex",
- baseUrl: VERTEX_BASE_URL,
- reasoning: true,
- input: ["text", "image"],
- cost: { input: 0.5, output: 3, cacheRead: 0.05, cacheWrite: 0 },
- contextWindow: 1048576,
- maxTokens: 65536,
- },
- {
- id: "gemini-2.0-flash",
- name: "Gemini 2.0 Flash (Vertex)",
- api: "google-vertex",
- provider: "google-vertex",
- baseUrl: VERTEX_BASE_URL,
- reasoning: false,
- input: ["text", "image"],
- cost: { input: 0.15, output: 0.6, cacheRead: 0.0375, cacheWrite: 0 },
- contextWindow: 1048576,
- maxTokens: 8192,
- },
- {
- id: "gemini-2.0-flash-lite",
- name: "Gemini 2.0 Flash Lite (Vertex)",
- api: "google-vertex",
- provider: "google-vertex",
- baseUrl: VERTEX_BASE_URL,
- reasoning: true,
- input: ["text", "image"],
- cost: { input: 0.075, output: 0.3, cacheRead: 0.01875, cacheWrite: 0 },
- contextWindow: 1048576,
- maxTokens: 65536,
- },
- {
- id: "gemini-2.5-pro",
- name: "Gemini 2.5 Pro (Vertex)",
- api: "google-vertex",
- provider: "google-vertex",
- baseUrl: VERTEX_BASE_URL,
- reasoning: true,
- input: ["text", "image"],
- cost: { input: 1.25, output: 10, cacheRead: 0.125, cacheWrite: 0 },
- contextWindow: 1048576,
- maxTokens: 65536,
- },
- {
- id: "gemini-2.5-flash",
- name: "Gemini 2.5 Flash (Vertex)",
- api: "google-vertex",
- provider: "google-vertex",
- baseUrl: VERTEX_BASE_URL,
- reasoning: true,
- input: ["text", "image"],
- cost: { input: 0.3, output: 2.5, cacheRead: 0.03, cacheWrite: 0 },
- contextWindow: 1048576,
- maxTokens: 65536,
- },
- {
- id: "gemini-2.5-flash-lite-preview-09-2025",
- name: "Gemini 2.5 Flash Lite Preview 09-25 (Vertex)",
- api: "google-vertex",
- provider: "google-vertex",
- baseUrl: VERTEX_BASE_URL,
- reasoning: true,
- input: ["text", "image"],
- cost: { input: 0.1, output: 0.4, cacheRead: 0.01, cacheWrite: 0 },
- contextWindow: 1048576,
- maxTokens: 65536,
- },
- {
- id: "gemini-2.5-flash-lite",
- name: "Gemini 2.5 Flash Lite (Vertex)",
- api: "google-vertex",
- provider: "google-vertex",
- baseUrl: VERTEX_BASE_URL,
- reasoning: true,
- input: ["text", "image"],
- cost: { input: 0.1, output: 0.4, cacheRead: 0.01, cacheWrite: 0 },
- contextWindow: 1048576,
- maxTokens: 65536,
- },
- {
- id: "gemini-1.5-pro",
- name: "Gemini 1.5 Pro (Vertex)",
- api: "google-vertex",
- provider: "google-vertex",
- baseUrl: VERTEX_BASE_URL,
- reasoning: false,
- input: ["text", "image"],
- cost: { input: 1.25, output: 5, cacheRead: 0.3125, cacheWrite: 0 },
- contextWindow: 1000000,
- maxTokens: 8192,
- },
- {
- id: "gemini-1.5-flash",
- name: "Gemini 1.5 Flash (Vertex)",
- api: "google-vertex",
- provider: "google-vertex",
- baseUrl: VERTEX_BASE_URL,
- reasoning: false,
- input: ["text", "image"],
- cost: { input: 0.075, output: 0.3, cacheRead: 0.01875, cacheWrite: 0 },
- contextWindow: 1000000,
- maxTokens: 8192,
- },
- {
- id: "gemini-1.5-flash-8b",
- name: "Gemini 1.5 Flash-8B (Vertex)",
- api: "google-vertex",
- provider: "google-vertex",
- baseUrl: VERTEX_BASE_URL,
- reasoning: false,
- input: ["text", "image"],
- cost: { input: 0.0375, output: 0.15, cacheRead: 0.01, cacheWrite: 0 },
- contextWindow: 1000000,
- maxTokens: 8192,
- },
- ];
- allModels.push(...vertexModels);
+ maxTokens: 30000,
+ });
+ }
// Azure Foundry deploys these with larger context windows than OpenAI's own API,
// which caps gpt-5.4/gpt-5.5 at 272k. See models-sold-directly-by-azure docs.
diff --git a/packages/ai/src/api/anthropic-messages.ts b/packages/ai/src/api/anthropic-messages.ts
index 1e3ef0be..a546db75 100644
--- a/packages/ai/src/api/anthropic-messages.ts
+++ b/packages/ai/src/api/anthropic-messages.ts
@@ -5,6 +5,7 @@ import type {
MessageCreateParamsStreaming,
MessageParam,
RawMessageStreamEvent,
+ RefusalStopDetails,
} from "@anthropic-ai/sdk/resources/messages.js";
import { calculateCost } from "../models.ts";
import type {
@@ -16,6 +17,7 @@ import type {
ImageContent,
Message,
Model,
+ ProviderEnv,
SimpleStreamOptions,
StopReason,
StreamFunction,
@@ -29,6 +31,7 @@ import type {
import { AssistantMessageEventStream } from "../utils/event-stream.ts";
import { headersToRecord } from "../utils/headers.ts";
import { parseJsonWithRepair, parseStreamingJson } from "../utils/json-parse.ts";
+import { getProviderEnvValue } from "../utils/provider-env.ts";
import { sanitizeSurrogates } from "../utils/sanitize-unicode.ts";
import { resolveCloudflareBaseUrl } from "./cloudflare.ts";
@@ -40,11 +43,11 @@ import { transformMessages } from "./transform-messages.ts";
* Resolve cache retention preference.
* Defaults to "short" and uses PI_CACHE_RETENTION for backward compatibility.
*/
-function resolveCacheRetention(cacheRetention?: CacheRetention): CacheRetention {
+function resolveCacheRetention(cacheRetention?: CacheRetention, env?: ProviderEnv): CacheRetention {
if (cacheRetention) {
return cacheRetention;
}
- if (typeof process !== "undefined" && process.env.PI_CACHE_RETENTION === "long") {
+ if (getProviderEnvValue("PI_CACHE_RETENTION", env) === "long") {
return "long";
}
return "short";
@@ -53,8 +56,9 @@ function resolveCacheRetention(cacheRetention?: CacheRetention): CacheRetention
function getCacheControl(
model: Model<"anthropic-messages">,
cacheRetention?: CacheRetention,
+ env?: ProviderEnv,
): { retention: CacheRetention; cacheControl?: CacheControlEphemeral } {
- const retention = resolveCacheRetention(cacheRetention);
+ const retention = resolveCacheRetention(cacheRetention, env);
if (retention === "none") {
return { retention };
}
@@ -493,7 +497,7 @@ export const stream: StreamFunction<"anthropic-messages", AnthropicOptions> = (
});
}
- const cacheRetention = options?.cacheRetention ?? resolveCacheRetention();
+ const cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);
const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
const created = createClient(
@@ -504,6 +508,7 @@ export const stream: StreamFunction<"anthropic-messages", AnthropicOptions> = (
options?.headers,
copilotDynamicHeaders,
cacheSessionId,
+ options?.env,
);
client = created.client;
isOAuth = created.isOAuthToken;
@@ -534,6 +539,7 @@ export const stream: StreamFunction<"anthropic-messages", AnthropicOptions> = (
output.usage.output = event.message.usage.output_tokens || 0;
output.usage.cacheRead = event.message.usage.cache_read_input_tokens || 0;
output.usage.cacheWrite = event.message.usage.cache_creation_input_tokens || 0;
+ output.usage.cacheWrite1h = event.message.usage.cache_creation?.ephemeral_1h_input_tokens || 0;
// Anthropic doesn't provide total_tokens, compute from components
output.usage.totalTokens =
output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
@@ -660,7 +666,11 @@ export const stream: StreamFunction<"anthropic-messages", AnthropicOptions> = (
}
} else if (event.type === "message_delta") {
if (event.delta.stop_reason) {
- output.stopReason = mapStopReason(event.delta.stop_reason);
+ const stopReasonResult = mapStopReason(event.delta.stop_reason, event.delta.stop_details);
+ output.stopReason = stopReasonResult.stopReason;
+ if (stopReasonResult.errorMessage) {
+ output.errorMessage = stopReasonResult.errorMessage;
+ }
}
// Only update usage fields if present (not null).
// Preserves input_tokens from message_start when proxies omit it in message_delta.
@@ -688,7 +698,7 @@ export const stream: StreamFunction<"anthropic-messages", AnthropicOptions> = (
}
if (output.stopReason === "aborted" || output.stopReason === "error") {
- throw new Error("An unknown error occurred");
+ throw new Error(output.errorMessage || "An unknown error occurred");
}
stream.push({ type: "done", reason: output.stopReason, message: output });
@@ -788,6 +798,7 @@ function createClient(
optionsHeaders?: Record,
dynamicHeaders?: Record,
sessionId?: string,
+ env?: ProviderEnv,
): { client: Anthropic; isOAuthToken: boolean } {
// Adaptive thinking models have interleaved thinking built in, so skip the beta header.
const needsInterleavedBeta = interleavedThinking && model.compat?.forceAdaptiveThinking !== true;
@@ -803,7 +814,7 @@ function createClient(
const client = new Anthropic({
apiKey: null,
authToken: null,
- baseURL: resolveCloudflareBaseUrl(model),
+ baseURL: resolveCloudflareBaseUrl(model, env),
dangerouslyAllowBrowser: true,
defaultHeaders: mergeHeaders(
{
@@ -896,7 +907,7 @@ function buildParams(
isOAuthToken: boolean,
options?: AnthropicOptions,
): MessageCreateParamsStreaming {
- const { cacheControl } = getCacheControl(model, options?.cacheRetention);
+ const { cacheControl } = getCacheControl(model, options?.cacheRetention, options?.env);
const compat = getAnthropicCompat(model);
const params: MessageCreateParamsStreaming = {
model: model.id,
@@ -1202,22 +1213,28 @@ function convertTools(
});
}
-function mapStopReason(reason: Anthropic.Messages.StopReason | string): StopReason {
+function mapStopReason(
+ reason: Anthropic.Messages.StopReason | string,
+ stopDetails?: RefusalStopDetails | null,
+): { stopReason: StopReason; errorMessage?: string } {
switch (reason) {
case "end_turn":
- return "stop";
+ return { stopReason: "stop" };
case "max_tokens":
- return "length";
+ return { stopReason: "length" };
case "tool_use":
- return "toolUse";
+ return { stopReason: "toolUse" };
case "refusal":
- return "error";
+ return {
+ stopReason: "error",
+ errorMessage: stopDetails?.explanation || `The model refused to complete the request`,
+ };
case "pause_turn": // Stop is good enough -> resubmit
- return "stop";
+ return { stopReason: "stop" };
case "stop_sequence":
- return "stop"; // We don't supply stop sequences, so this should never happen
+ return { stopReason: "stop" }; // We don't supply stop sequences, so this should never happen
case "sensitive": // Content flagged by safety filters (not yet in SDK types)
- return "error";
+ return { stopReason: "error" };
default:
// Handle unknown stop reasons gracefully (API may add new values)
throw new Error(`Unhandled stop reason: ${reason}`);
diff --git a/packages/ai/src/api/azure-openai-responses.ts b/packages/ai/src/api/azure-openai-responses.ts
index 93660334..8150a82b 100644
--- a/packages/ai/src/api/azure-openai-responses.ts
+++ b/packages/ai/src/api/azure-openai-responses.ts
@@ -12,6 +12,7 @@ import type {
} from "../types.ts";
import { AssistantMessageEventStream } from "../utils/event-stream.ts";
import { headersToRecord } from "../utils/headers.ts";
+import { getProviderEnvValue } from "../utils/provider-env.ts";
import { clampOpenAIPromptCacheKey } from "./openai-prompt-cache.ts";
import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.ts";
import { buildBaseOptions } from "./simple-options.ts";
@@ -36,7 +37,9 @@ function resolveDeploymentName(model: Model<"azure-openai-responses">, options?:
if (options?.azureDeploymentName) {
return options.azureDeploymentName;
}
- const mappedDeployment = parseDeploymentNameMap(process.env.AZURE_OPENAI_DEPLOYMENT_NAME_MAP).get(model.id);
+ const mappedDeployment = parseDeploymentNameMap(
+ getProviderEnvValue("AZURE_OPENAI_DEPLOYMENT_NAME_MAP", options?.env),
+ ).get(model.id);
return mappedDeployment || model.id;
}
@@ -198,10 +201,14 @@ function resolveAzureConfig(
model: Model<"azure-openai-responses">,
options?: AzureOpenAIResponsesOptions,
): { baseUrl: string; apiVersion: string } {
- const apiVersion = options?.azureApiVersion || process.env.AZURE_OPENAI_API_VERSION || DEFAULT_AZURE_API_VERSION;
+ const apiVersion =
+ options?.azureApiVersion ||
+ getProviderEnvValue("AZURE_OPENAI_API_VERSION", options?.env) ||
+ DEFAULT_AZURE_API_VERSION;
- const baseUrl = options?.azureBaseUrl?.trim() || process.env.AZURE_OPENAI_BASE_URL?.trim() || undefined;
- const resourceName = options?.azureResourceName || process.env.AZURE_OPENAI_RESOURCE_NAME;
+ const baseUrl =
+ options?.azureBaseUrl?.trim() || getProviderEnvValue("AZURE_OPENAI_BASE_URL", options?.env)?.trim() || undefined;
+ const resourceName = options?.azureResourceName || getProviderEnvValue("AZURE_OPENAI_RESOURCE_NAME", options?.env);
let resolvedBaseUrl = baseUrl;
diff --git a/packages/ai/src/api/bedrock-converse-stream.ts b/packages/ai/src/api/bedrock-converse-stream.ts
index ac6e9f29..51d609fc 100644
--- a/packages/ai/src/api/bedrock-converse-stream.ts
+++ b/packages/ai/src/api/bedrock-converse-stream.ts
@@ -1,3 +1,4 @@
+import type { Agent as HttpsAgent } from "node:https";
import {
BedrockRuntimeClient,
type BedrockRuntimeClientConfig,
@@ -23,6 +24,8 @@ import {
} from "@aws-sdk/client-bedrock-runtime";
import { NodeHttpHandler } from "@smithy/node-http-handler";
import type { BuildMiddleware, DocumentType, MetadataBearer } from "@smithy/types";
+import { HttpProxyAgent } from "http-proxy-agent";
+import { HttpsProxyAgent } from "https-proxy-agent";
import { calculateCost } from "../models.ts";
import type {
Api,
@@ -31,6 +34,7 @@ import type {
Context,
ImageContent,
Model,
+ ProviderEnv,
SimpleStreamOptions,
StopReason,
StreamFunction,
@@ -45,7 +49,8 @@ import type {
} from "../types.ts";
import { AssistantMessageEventStream } from "../utils/event-stream.ts";
import { parseStreamingJson } from "../utils/json-parse.ts";
-import { createHttpProxyAgentsForTarget } from "../utils/node-http-proxy.ts";
+import { resolveHttpProxyUrlForTarget } from "../utils/node-http-proxy.ts";
+import { getProviderEnvValue } from "../utils/provider-env.ts";
import { sanitizeSurrogates } from "../utils/sanitize-unicode.ts";
import { adjustMaxTokensForThinking, buildBaseOptions, clampReasoning } from "./simple-options.ts";
import { transformMessages } from "./transform-messages.ts";
@@ -119,18 +124,18 @@ export const stream: StreamFunction<"bedrock-converse-stream", BedrockOptions> =
const blocks = output.content as Block[];
const config: BedrockRuntimeClientConfig = {
- profile: options.profile,
+ profile: options.profile || getProviderEnvValue("AWS_PROFILE", options.env),
};
const configuredRegion = getConfiguredBedrockRegion(options);
- const hasConfiguredProfile = hasConfiguredBedrockProfile();
+ const hasAmbientConfiguredProfile = Boolean(getProviderEnvValue("AWS_PROFILE"));
const endpointRegion = getStandardBedrockEndpointRegion(model.baseUrl);
const useExplicitEndpoint = shouldUseExplicitBedrockEndpoint(
model.baseUrl,
configuredRegion,
- hasConfiguredProfile,
+ hasAmbientConfiguredProfile,
);
- // Only pin standard AWS Bedrock runtime endpoints when no region/profile is configured.
+ // Only pin standard AWS Bedrock runtime endpoints when no region or ambient AWS_PROFILE is configured.
// This preserves custom endpoints (VPC/proxy) from #3402 without forcing built-in
// catalog defaults such as us-east-1 to override AWS_REGION/AWS_PROFILE.
if (useExplicitEndpoint) {
@@ -138,8 +143,10 @@ export const stream: StreamFunction<"bedrock-converse-stream", BedrockOptions> =
}
// Resolve bearer token for Bedrock API key auth.
- const bearerToken = options.bearerToken || process.env.AWS_BEARER_TOKEN_BEDROCK || undefined;
- const useBearerToken = bearerToken !== undefined && process.env.AWS_BEDROCK_SKIP_AUTH !== "1";
+ const skipAuth = getProviderEnvValue("AWS_BEDROCK_SKIP_AUTH", options.env) === "1";
+ const bearerToken =
+ options.bearerToken || getProviderEnvValue("AWS_BEARER_TOKEN_BEDROCK", options.env) || undefined;
+ const useBearerToken = bearerToken !== undefined && !skipAuth;
// in Node.js/Bun environment only
if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
@@ -153,25 +160,33 @@ export const stream: StreamFunction<"bedrock-converse-stream", BedrockOptions> =
config.region = configuredRegion;
} else if (endpointRegion && useExplicitEndpoint) {
config.region = endpointRegion;
- } else if (!hasConfiguredProfile) {
+ } else if (!hasAmbientConfiguredProfile) {
config.region = "us-east-1";
}
// Support proxies that don't need authentication
- if (process.env.AWS_BEDROCK_SKIP_AUTH === "1") {
+ if (skipAuth) {
config.credentials = {
accessKeyId: "dummy-access-key",
secretAccessKey: "dummy-secret-key",
};
}
- const proxyAgents = createHttpProxyAgentsForTarget(model.baseUrl);
- if (proxyAgents) {
+ const credentials = getConfiguredBedrockCredentials(options.env);
+ if (!skipAuth && credentials) {
+ config.credentials = credentials;
+ }
+
+ const proxyUrl = resolveHttpProxyUrlForTarget(model.baseUrl, options.env);
+ if (proxyUrl) {
// Bedrock runtime uses NodeHttp2Handler by default since v3.798.0, which is based
// on `http2` module and has no support for http agent.
// Use NodeHttpHandler to support HTTP(S) proxy agents.
- config.requestHandler = new NodeHttpHandler(proxyAgents);
- } else if (process.env.AWS_BEDROCK_FORCE_HTTP1 === "1") {
+ config.requestHandler = new NodeHttpHandler({
+ httpAgent: new HttpProxyAgent(proxyUrl),
+ httpsAgent: new HttpsProxyAgent(proxyUrl) as unknown as HttpsAgent,
+ });
+ } else if (getProviderEnvValue("AWS_BEDROCK_FORCE_HTTP1", options.env) === "1") {
// Some custom endpoints require HTTP/1.1 instead of HTTP/2
config.requestHandler = new NodeHttpHandler();
}
@@ -192,12 +207,12 @@ export const stream: StreamFunction<"bedrock-converse-stream", BedrockOptions> =
if (options.headers && Object.keys(options.headers).length > 0) {
addCustomHeadersMiddleware(client, options.headers);
}
- const cacheRetention = resolveCacheRetention(options.cacheRetention);
+ const cacheRetention = resolveCacheRetention(options.cacheRetention, options.env);
const inferenceMaxTokens = options.maxTokens ?? (isAnthropicClaudeModel(model) ? model.maxTokens : undefined);
let commandInput = {
modelId: model.id,
- messages: convertMessages(context, model, cacheRetention),
- system: buildSystemPrompt(context.systemPrompt, model, cacheRetention),
+ messages: convertMessages(context, model, cacheRetention, options.env),
+ system: buildSystemPrompt(context.systemPrompt, model, cacheRetention, options.env),
inferenceConfig: {
...(inferenceMaxTokens !== undefined && { maxTokens: inferenceMaxTokens }),
...(options.temperature !== undefined && { temperature: options.temperature }),
@@ -578,11 +593,11 @@ function mapThinkingLevelToEffort(
* Resolve cache retention preference.
* Defaults to "short" and uses PI_CACHE_RETENTION for backward compatibility.
*/
-function resolveCacheRetention(cacheRetention?: CacheRetention): CacheRetention {
+function resolveCacheRetention(cacheRetention?: CacheRetention, env?: ProviderEnv): CacheRetention {
if (cacheRetention) {
return cacheRetention;
}
- if (typeof process !== "undefined" && process.env.PI_CACHE_RETENTION === "long") {
+ if (getProviderEnvValue("PI_CACHE_RETENTION", env) === "long") {
return "long";
}
return "short";
@@ -617,14 +632,14 @@ function isAnthropicClaudeModel(model: Model<"bedrock-converse-stream">): boolea
* As a last resort, set AWS_BEDROCK_FORCE_CACHE=1 to enable cache points.
* Amazon Nova models have automatic caching and don't need explicit cache points.
*/
-function supportsPromptCaching(model: Model<"bedrock-converse-stream">): boolean {
+function supportsPromptCaching(model: Model<"bedrock-converse-stream">, env?: ProviderEnv): boolean {
const candidates = getModelMatchCandidates(model.id, model.name);
const hasClaudeRef = candidates.some((s) => s.includes("claude"));
if (!hasClaudeRef) {
// Application inference profiles don't contain the model name in the ARN.
// Allow users to force cache points via environment variable.
- if (typeof process !== "undefined" && process.env.AWS_BEDROCK_FORCE_CACHE === "1") return true;
+ if (getProviderEnvValue("AWS_BEDROCK_FORCE_CACHE", env) === "1") return true;
return false;
}
// Claude 4.x models (opus-4, sonnet-4, haiku-4)
@@ -652,13 +667,14 @@ function buildSystemPrompt(
systemPrompt: string | undefined,
model: Model<"bedrock-converse-stream">,
cacheRetention: CacheRetention,
+ env?: ProviderEnv,
): SystemContentBlock[] | undefined {
if (!systemPrompt) return undefined;
const blocks: SystemContentBlock[] = [{ text: sanitizeSurrogates(systemPrompt) }];
// Add cache point for supported Claude models when caching is enabled
- if (cacheRetention !== "none" && supportsPromptCaching(model)) {
+ if (cacheRetention !== "none" && supportsPromptCaching(model, env)) {
blocks.push({
cachePoint: { type: CachePointType.DEFAULT, ...(cacheRetention === "long" ? { ttl: CacheTTL.ONE_HOUR } : {}) },
});
@@ -699,6 +715,7 @@ function convertMessages(
context: Context,
model: Model<"bedrock-converse-stream">,
cacheRetention: CacheRetention,
+ env?: ProviderEnv,
): Message[] {
const result: Message[] = [];
const transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);
@@ -844,7 +861,7 @@ function convertMessages(
}
// Add cache point to the last user message for supported Claude models when caching is enabled
- if (cacheRetention !== "none" && supportsPromptCaching(model) && result.length > 0) {
+ if (cacheRetention !== "none" && supportsPromptCaching(model, env) && result.length > 0) {
const lastMessage = result[result.length - 1];
if (lastMessage.role === ConversationRole.USER && lastMessage.content) {
(lastMessage.content as ContentBlock[]).push({
@@ -906,19 +923,26 @@ function mapStopReason(reason: string | undefined): StopReason {
}
function getConfiguredBedrockRegion(options: BedrockOptions): string | undefined {
- if (typeof process === "undefined") {
- return options.region;
- }
-
- return options.region || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || undefined;
+ return (
+ options.region ||
+ getProviderEnvValue("AWS_REGION", options.env) ||
+ getProviderEnvValue("AWS_DEFAULT_REGION", options.env) ||
+ undefined
+ );
}
-function hasConfiguredBedrockProfile(): boolean {
- if (typeof process === "undefined") {
- return false;
+function getConfiguredBedrockCredentials(env?: ProviderEnv): BedrockRuntimeClientConfig["credentials"] | undefined {
+ const accessKeyId = getProviderEnvValue("AWS_ACCESS_KEY_ID", env);
+ const secretAccessKey = getProviderEnvValue("AWS_SECRET_ACCESS_KEY", env);
+ if (!accessKeyId || !secretAccessKey) {
+ return undefined;
}
-
- return Boolean(process.env.AWS_PROFILE);
+ const sessionToken = getProviderEnvValue("AWS_SESSION_TOKEN", env);
+ return {
+ accessKeyId,
+ secretAccessKey,
+ ...(sessionToken ? { sessionToken } : {}),
+ };
}
function getStandardBedrockEndpointRegion(baseUrl: string | undefined): string | undefined {
@@ -938,14 +962,14 @@ function getStandardBedrockEndpointRegion(baseUrl: string | undefined): string |
function shouldUseExplicitBedrockEndpoint(
baseUrl: string,
configuredRegion: string | undefined,
- hasConfiguredProfile: boolean,
+ hasAmbientConfiguredProfile: boolean,
): boolean {
const endpointRegion = getStandardBedrockEndpointRegion(baseUrl);
if (!endpointRegion) {
return true;
}
- return !configuredRegion && !hasConfiguredProfile;
+ return !configuredRegion && !hasAmbientConfiguredProfile;
}
function isGovCloudBedrockTarget(model: Model<"bedrock-converse-stream">, options: BedrockOptions): boolean {
diff --git a/packages/ai/src/api/cloudflare.ts b/packages/ai/src/api/cloudflare.ts
index cd0a8159..98546419 100644
--- a/packages/ai/src/api/cloudflare.ts
+++ b/packages/ai/src/api/cloudflare.ts
@@ -1,4 +1,5 @@
-import type { Api, Model } from "../types.ts";
+import type { Api, Model, ProviderEnv } from "../types.ts";
+import { getProviderEnvValue } from "../utils/provider-env.ts";
/** Workers AI direct endpoint. */
export const CLOUDFLARE_WORKERS_AI_BASE_URL =
@@ -20,12 +21,12 @@ export function isCloudflareProvider(provider: string): boolean {
return provider === "cloudflare-workers-ai" || provider === "cloudflare-ai-gateway";
}
-/** Substitute `{VAR}` placeholders in a Cloudflare baseUrl from process.env. */
-export function resolveCloudflareBaseUrl(model: Model): string {
+/** Substitute `{VAR}` placeholders in a Cloudflare baseUrl from provider env or process.env. */
+export function resolveCloudflareBaseUrl(model: Model, env?: ProviderEnv): string {
const url = model.baseUrl;
if (!url.includes("{")) return url;
const baseUrl = url.replace(/\{([A-Z_][A-Z0-9_]*)\}/g, (_match, name: string) => {
- const value = process.env[name];
+ const value = getProviderEnvValue(name, env);
if (!value) {
throw new Error(`${name} is required for provider ${model.provider} but is not set.`);
}
diff --git a/packages/ai/src/api/google-generative-ai.ts b/packages/ai/src/api/google-generative-ai.ts
index f959a6aa..d28f97c4 100644
--- a/packages/ai/src/api/google-generative-ai.ts
+++ b/packages/ai/src/api/google-generative-ai.ts
@@ -406,7 +406,8 @@ function isGemini3ProModel(model: Model<"google-generative-ai">): boolean {
}
function isGemini3FlashModel(model: Model<"google-generative-ai">): boolean {
- return /gemini-3(?:\.\d+)?-flash/.test(model.id.toLowerCase());
+ const id = model.id.toLowerCase();
+ return /gemini-3(?:\.\d+)?-flash/.test(id) || id === "gemini-flash-latest" || id === "gemini-flash-lite-latest";
}
function getDisabledThinkingConfig(model: Model<"google-generative-ai">): ThinkingConfig {
diff --git a/packages/ai/src/api/google-vertex.ts b/packages/ai/src/api/google-vertex.ts
index 1e122a88..79087c61 100644
--- a/packages/ai/src/api/google-vertex.ts
+++ b/packages/ai/src/api/google-vertex.ts
@@ -14,6 +14,7 @@ import type {
Context,
Model,
ThinkingLevel as PiThinkingLevel,
+ ProviderEnv,
SimpleStreamOptions,
StreamFunction,
StreamOptions,
@@ -23,6 +24,7 @@ import type {
ToolCall,
} from "../types.ts";
import { AssistantMessageEventStream } from "../utils/event-stream.ts";
+import { getProviderEnvValue } from "../utils/provider-env.ts";
import { sanitizeSurrogates } from "../utils/sanitize-unicode.ts";
import type { GoogleThinkingLevel } from "./google-shared.ts";
import {
@@ -91,7 +93,7 @@ export const stream: StreamFunction<"google-vertex", GoogleVertexOptions> = (
// Create the client using either a Vertex API key, if provided, or ADC with project and location
const client = apiKey
? createClientWithApiKey(model, apiKey, options?.headers)
- : createClient(model, resolveProject(options), resolveLocation(options), options?.headers);
+ : createClient(model, resolveProject(options), resolveLocation(options), options?.headers, options?.env);
let params = buildParams(model, context, options);
const nextParams = await options?.onPayload?.(params, model);
if (nextParams !== undefined) {
@@ -333,12 +335,15 @@ function createClient(
project: string,
location: string,
optionsHeaders?: Record,
+ env?: ProviderEnv,
): GoogleGenAI {
+ const googleAuthOptions = buildGoogleAuthOptions(env);
return new GoogleGenAI({
vertexai: true,
project,
location,
apiVersion: API_VERSION,
+ ...(googleAuthOptions ? { googleAuthOptions } : {}),
httpOptions: buildHttpOptions(model, optionsHeaders),
});
}
@@ -394,6 +399,11 @@ function baseUrlIncludesApiVersion(baseUrl: string): boolean {
}
}
+function buildGoogleAuthOptions(env?: ProviderEnv): { keyFilename: string } | undefined {
+ const keyFilename = getProviderEnvValue("GOOGLE_APPLICATION_CREDENTIALS", env);
+ return keyFilename ? { keyFilename } : undefined;
+}
+
function resolveApiKey(options?: GoogleVertexOptions): string | undefined {
const apiKey = options?.apiKey?.trim();
if (!apiKey || apiKey === GCP_VERTEX_CREDENTIALS_MARKER || isPlaceholderApiKey(apiKey)) {
@@ -407,7 +417,10 @@ function isPlaceholderApiKey(apiKey: string): boolean {
}
function resolveProject(options?: GoogleVertexOptions): string {
- const project = options?.project || process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT;
+ const project =
+ options?.project ||
+ getProviderEnvValue("GOOGLE_CLOUD_PROJECT", options?.env) ||
+ getProviderEnvValue("GCLOUD_PROJECT", options?.env);
if (!project) {
throw new Error(
"Vertex AI requires a project ID. Set GOOGLE_CLOUD_PROJECT/GCLOUD_PROJECT or pass project in options.",
@@ -417,7 +430,7 @@ function resolveProject(options?: GoogleVertexOptions): string {
}
function resolveLocation(options?: GoogleVertexOptions): string {
- const location = options?.location || process.env.GOOGLE_CLOUD_LOCATION;
+ const location = options?.location || getProviderEnvValue("GOOGLE_CLOUD_LOCATION", options?.env);
if (!location) {
throw new Error("Vertex AI requires a location. Set GOOGLE_CLOUD_LOCATION or pass location in options.");
}
@@ -490,7 +503,8 @@ function isGemini3ProModel(model: Model<"google-generative-ai">): boolean {
}
function isGemini3FlashModel(model: Model<"google-generative-ai">): boolean {
- return /gemini-3(?:\.\d+)?-flash/.test(model.id.toLowerCase());
+ const id = model.id.toLowerCase();
+ return /gemini-3(?:\.\d+)?-flash/.test(id) || id === "gemini-flash-latest" || id === "gemini-flash-lite-latest";
}
function getDisabledThinkingConfig(model: Model<"google-vertex">): ThinkingConfig {
diff --git a/packages/ai/src/api/mistral-conversations.ts b/packages/ai/src/api/mistral-conversations.ts
index b4873af7..66519e5e 100644
--- a/packages/ai/src/api/mistral-conversations.ts
+++ b/packages/ai/src/api/mistral-conversations.ts
@@ -226,7 +226,7 @@ function buildRequestOptions(model: Model<"mistral-conversations">, options?: Mi
// Mistral infrastructure uses `x-affinity` for KV-cache reuse (prefix caching).
// Respect explicit caller-provided header values.
- if (options?.sessionId && !headers["x-affinity"]) {
+ if (shouldUsePromptCaching(options) && !headers["x-affinity"]) {
headers["x-affinity"] = options.sessionId;
}
@@ -255,6 +255,7 @@ function buildChatPayload(
if (options?.toolChoice) payload.toolChoice = mapToolChoice(options.toolChoice);
if (options?.promptMode) payload.promptMode = options.promptMode;
if (options?.reasoningEffort) payload.reasoningEffort = options.reasoningEffort;
+ if (shouldUsePromptCaching(options)) payload.promptCacheKey = options.sessionId;
if (context.systemPrompt) {
payload.messages.unshift({
@@ -266,6 +267,31 @@ function buildChatPayload(
return payload;
}
+function shouldUsePromptCaching(options?: MistralOptions): options is MistralOptions & { sessionId: string } {
+ return options?.cacheRetention !== "none" && !!options?.sessionId;
+}
+
+function getMistralCachedPromptTokens(usage: unknown, promptTokens: number): number {
+ const rawUsage = usage as {
+ promptTokensDetails?: { cachedTokens?: unknown } | null;
+ prompt_tokens_details?: { cached_tokens?: unknown } | null;
+ promptTokenDetails?: { cachedTokens?: unknown } | null;
+ prompt_token_details?: { cached_tokens?: unknown } | null;
+ numCachedTokens?: unknown;
+ num_cached_tokens?: unknown;
+ };
+ const rawCachedTokens =
+ rawUsage.promptTokensDetails?.cachedTokens ??
+ rawUsage.prompt_tokens_details?.cached_tokens ??
+ rawUsage.promptTokenDetails?.cachedTokens ??
+ rawUsage.prompt_token_details?.cached_tokens ??
+ rawUsage.numCachedTokens ??
+ rawUsage.num_cached_tokens ??
+ 0;
+ const cachedTokens = typeof rawCachedTokens === "number" && Number.isFinite(rawCachedTokens) ? rawCachedTokens : 0;
+ return Math.min(promptTokens, Math.max(0, cachedTokens));
+}
+
async function consumeChatStream(
model: Model<"mistral-conversations">,
output: AssistantMessage,
@@ -305,11 +331,16 @@ async function consumeChatStream(
output.responseId ||= chunk.id;
if (chunk.usage) {
- output.usage.input = chunk.usage.promptTokens || 0;
+ const promptTokens = chunk.usage.promptTokens || 0;
+ const cachedPromptTokens = getMistralCachedPromptTokens(chunk.usage, promptTokens);
+
+ output.usage.input = Math.max(0, promptTokens - cachedPromptTokens);
output.usage.output = chunk.usage.completionTokens || 0;
- output.usage.cacheRead = 0;
+ output.usage.cacheRead = cachedPromptTokens;
output.usage.cacheWrite = 0;
- output.usage.totalTokens = chunk.usage.totalTokens || output.usage.input + output.usage.output;
+ output.usage.totalTokens =
+ chunk.usage.totalTokens ||
+ output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
calculateCost(model, output.usage);
}
diff --git a/packages/ai/src/api/openai-codex-responses.ts b/packages/ai/src/api/openai-codex-responses.ts
index fe7d8dc3..ee2503d5 100644
--- a/packages/ai/src/api/openai-codex-responses.ts
+++ b/packages/ai/src/api/openai-codex-responses.ts
@@ -27,6 +27,7 @@ import type {
AssistantMessage,
Context,
Model,
+ ProviderEnv,
SimpleStreamOptions,
StreamFunction,
StreamOptions,
@@ -40,6 +41,7 @@ import {
} from "../utils/diagnostics.ts";
import { AssistantMessageEventStream } from "../utils/event-stream.ts";
import { headersToRecord } from "../utils/headers.ts";
+import { resolveHttpProxyUrlForTarget } from "../utils/node-http-proxy.ts";
import { clampOpenAIPromptCacheKey } from "./openai-prompt-cache.ts";
import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.ts";
import { buildBaseOptions } from "./simple-options.ts";
@@ -53,7 +55,9 @@ const JWT_CLAIM_PATH = "https://api.openai.com/auth" as const;
const DEFAULT_MAX_RETRIES = 0;
const BASE_DELAY_MS = 1000;
const DEFAULT_MAX_RETRY_DELAY_MS = 60_000;
-const DEFAULT_SSE_HEADER_TIMEOUT_MS = 10_000;
+// Keep a bounded pre-header timeout so zero-event Codex SSE stalls fail instead of
+// leaving callers stuck on "Working..." indefinitely. See #4945.
+const DEFAULT_SSE_HEADER_TIMEOUT_MS = 20_000;
const DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS = 15_000;
const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]);
const WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009;
@@ -812,19 +816,13 @@ type WebSocketConstructor = new (
) => WebSocketLike;
let _cachedWebsocket: WebSocketConstructor | null = null;
-async function getWebSocketConstructor(): Promise {
- if (_cachedWebsocket) return _cachedWebsocket;
+async function getWebSocketConstructor(env?: ProviderEnv): Promise {
+ if (!env && _cachedWebsocket) return _cachedWebsocket;
// bun doesn't respect http proxy envs, ref: https://github.com/oven-sh/bun/issues/15489
// TODO: remove this when bun supports proxy envs in websocket.
- if (
- process?.versions?.bun &&
- (process.env.HTTP_PROXY || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.https_proxy)
- ) {
- const m = await dynamicImport("proxy-from-env");
- const getProxyForUrl = (m as { getProxyForUrl: (url: string | object | URL) => string }).getProxyForUrl;
-
- _cachedWebsocket = class extends WebSocket {
+ if (typeof process !== "undefined" && process.versions?.bun) {
+ const WebSocketWithProxy = class extends WebSocket {
constructor(url: string | URL, options?: string | string[] | Record) {
let _opts: Record = {};
if (Array.isArray(options) || typeof options === "string") {
@@ -833,11 +831,17 @@ async function getWebSocketConstructor(): Promise {
_opts = { ...options };
}
- const proxy = getProxyForUrl(url.toString().replace(/^wss:/, "https:").replace(/^ws:/, "http:"));
- super(url, { ..._opts, ...(proxy ? { proxy } : {}) } as any);
+ const proxyUrl = resolveHttpProxyUrlForTarget(
+ url.toString().replace(/^wss:/, "https:").replace(/^ws:/, "http:"),
+ env,
+ );
+ super(url, { ..._opts, ...(proxyUrl ? { proxy: proxyUrl.toString() } : {}) } as any);
}
};
- return _cachedWebsocket;
+ if (!env) {
+ _cachedWebsocket = WebSocketWithProxy;
+ }
+ return WebSocketWithProxy;
}
const ctor = (globalThis as { WebSocket?: unknown }).WebSocket;
@@ -892,8 +896,9 @@ async function connectWebSocket(
headers: Headers,
signal?: AbortSignal,
connectTimeoutMs = DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS,
+ env?: ProviderEnv,
): Promise {
- const WebSocketCtor = await getWebSocketConstructor();
+ const WebSocketCtor = await getWebSocketConstructor(env);
if (!WebSocketCtor) {
throw new Error("WebSocket transport is not available in this runtime");
}
@@ -970,6 +975,7 @@ async function acquireWebSocket(
sessionId: string | undefined,
signal?: AbortSignal,
connectTimeoutMs?: number,
+ env?: ProviderEnv,
): Promise<{
socket: WebSocketLike;
entry?: CachedWebSocketConnection;
@@ -977,7 +983,7 @@ async function acquireWebSocket(
release: (options?: { keep?: boolean }) => void;
}> {
if (!sessionId) {
- const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs);
+ const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);
return {
socket,
reused: false,
@@ -1009,7 +1015,7 @@ async function acquireWebSocket(
};
}
if (cached.busy) {
- const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs);
+ const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);
return {
socket,
reused: false,
@@ -1024,7 +1030,7 @@ async function acquireWebSocket(
}
}
- const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs);
+ const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);
const entry: CachedWebSocketConnection = { socket, busy: true };
websocketSessionCache.set(sessionId, entry);
return {
@@ -1310,6 +1316,7 @@ async function processWebSocketStream(
options?.sessionId,
options?.signal,
websocketConnectTimeoutMs,
+ options?.env,
);
let keepConnection = true;
const useCachedContext = options?.transport === "websocket-cached" || options?.transport === "auto";
diff --git a/packages/ai/src/api/openai-completions.ts b/packages/ai/src/api/openai-completions.ts
index 41f4c7a8..e891365e 100644
--- a/packages/ai/src/api/openai-completions.ts
+++ b/packages/ai/src/api/openai-completions.ts
@@ -14,11 +14,13 @@ import { calculateCost, clampThinkingLevel } from "../models.ts";
import type {
AssistantMessage,
CacheRetention,
+ ChatTemplateKwargValue,
Context,
ImageContent,
Message,
Model,
OpenAICompletionsCompat,
+ ProviderEnv,
SimpleStreamOptions,
StopReason,
StreamFunction,
@@ -32,6 +34,7 @@ import type {
import { AssistantMessageEventStream } from "../utils/event-stream.ts";
import { headersToRecord } from "../utils/headers.ts";
import { parseStreamingJson } from "../utils/json-parse.ts";
+import { getProviderEnvValue } from "../utils/provider-env.ts";
import { sanitizeSurrogates } from "../utils/sanitize-unicode.ts";
import { isCloudflareProvider, resolveCloudflareBaseUrl } from "./cloudflare.ts";
import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.ts";
@@ -74,6 +77,20 @@ function isImageContentBlock(block: { type: string }): block is ImageContent {
return block.type === "image";
}
+function isEncryptedReasoningDetail(detail: unknown): detail is OpenAIEncryptedReasoningDetail {
+ if (typeof detail !== "object" || detail === null) {
+ return false;
+ }
+ const candidate = detail as Record;
+ return (
+ candidate.type === "reasoning.encrypted" &&
+ typeof candidate.id === "string" &&
+ candidate.id.length > 0 &&
+ typeof candidate.data === "string" &&
+ candidate.data.length > 0
+ );
+}
+
export interface OpenAICompletionsOptions extends StreamOptions {
toolChoice?: "auto" | "none" | "required" | { type: "function"; function: { name: string } };
reasoningEffort?: "minimal" | "low" | "medium" | "high" | "xhigh";
@@ -88,8 +105,16 @@ type ResolvedOpenAICompletionsCompat = Omit, "
cacheControlFormat?: OpenAICompletionsCompat["cacheControlFormat"];
};
+type ResolvedChatTemplateKwargValue = string | number | boolean | null;
+
type ChatCompletionInstructionMessageParam = ChatCompletionDeveloperMessageParam | ChatCompletionSystemMessageParam;
+type OpenAIEncryptedReasoningDetail = {
+ type: "reasoning.encrypted";
+ id: string;
+ data: string;
+};
+
type ChatCompletionTextPartWithCacheControl = ChatCompletionContentPartText & {
cache_control?: OpenAICompatCacheControl;
};
@@ -98,11 +123,11 @@ type ChatCompletionToolWithCacheControl = OpenAI.Chat.Completions.ChatCompletion
cache_control?: OpenAICompatCacheControl;
};
-function resolveCacheRetention(cacheRetention?: CacheRetention): CacheRetention {
+function resolveCacheRetention(cacheRetention?: CacheRetention, env?: ProviderEnv): CacheRetention {
if (cacheRetention) {
return cacheRetention;
}
- if (typeof process !== "undefined" && process.env.PI_CACHE_RETENTION === "long") {
+ if (getProviderEnvValue("PI_CACHE_RETENTION", env) === "long") {
return "long";
}
return "short";
@@ -140,9 +165,9 @@ export const stream: StreamFunction<"openai-completions", OpenAICompletionsOptio
throw new Error(`No API key for provider: ${model.provider}`);
}
const compat = getCompat(model);
- const cacheRetention = resolveCacheRetention(options?.cacheRetention);
+ const cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);
const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
- const client = createClient(model, context, apiKey, options?.headers, cacheSessionId, compat);
+ const client = createClient(model, context, apiKey, options?.headers, cacheSessionId, compat, options?.env);
let params = buildParams(model, context, options, compat, cacheRetention);
const nextParams = await options?.onPayload?.(params, model);
if (nextParams !== undefined) {
@@ -171,6 +196,7 @@ export const stream: StreamFunction<"openai-completions", OpenAICompletionsOptio
let hasFinishReason = false;
const toolCallBlocksByIndex = new Map();
const toolCallBlocksById = new Map();
+ const pendingReasoningDetailsByToolCallId = new Map();
const blocks = output.content as StreamingBlock[];
const getContentIndex = (block: StreamingBlock) => blocks.indexOf(block);
const finishBlock = (block: StreamingBlock) => {
@@ -226,6 +252,16 @@ export const stream: StreamFunction<"openai-completions", OpenAICompletionsOptio
}
return thinkingBlock;
};
+ const applyPendingReasoningDetail = (block: StreamingToolCallBlock) => {
+ if (!block.id) {
+ return;
+ }
+ const pendingReasoningDetail = pendingReasoningDetailsByToolCallId.get(block.id);
+ if (pendingReasoningDetail) {
+ block.thoughtSignature = pendingReasoningDetail;
+ pendingReasoningDetailsByToolCallId.delete(block.id);
+ }
+ };
const ensureToolCallBlock = (toolCall: StreamingToolCallDelta) => {
const streamIndex = typeof toolCall.index === "number" ? toolCall.index : undefined;
let block = streamIndex !== undefined ? toolCallBlocksByIndex.get(streamIndex) : undefined;
@@ -261,6 +297,7 @@ export const stream: StreamFunction<"openai-completions", OpenAICompletionsOptio
if (toolCall.id) {
toolCallBlocksById.set(toolCall.id, block);
}
+ applyPendingReasoningDetail(block);
return block;
};
@@ -370,15 +407,16 @@ export const stream: StreamFunction<"openai-completions", OpenAICompletionsOptio
}
}
- const reasoningDetails = (choice.delta as any).reasoning_details;
- if (reasoningDetails && Array.isArray(reasoningDetails)) {
+ const reasoningDetails = (choice.delta as { reasoning_details?: unknown }).reasoning_details;
+ if (Array.isArray(reasoningDetails)) {
for (const detail of reasoningDetails) {
- if (detail.type === "reasoning.encrypted" && detail.id && detail.data) {
- const matchingToolCall = output.content.find(
- (b) => b.type === "toolCall" && b.id === detail.id,
- ) as ToolCall | undefined;
+ if (isEncryptedReasoningDetail(detail)) {
+ const serializedDetail = JSON.stringify(detail);
+ const matchingToolCall = toolCallBlocksById.get(detail.id);
if (matchingToolCall) {
- matchingToolCall.thoughtSignature = JSON.stringify(detail);
+ matchingToolCall.thoughtSignature = serializedDetail;
+ } else {
+ pendingReasoningDetailsByToolCallId.set(detail.id, serializedDetail);
}
}
}
@@ -454,6 +492,7 @@ function createClient(
optionsHeaders?: Record,
sessionId?: string,
compat: ResolvedOpenAICompletionsCompat = getCompat(model),
+ env?: ProviderEnv,
) {
const headers = { ...model.headers };
if (model.provider === "github-copilot") {
@@ -487,7 +526,7 @@ function createClient(
return new OpenAI({
apiKey,
- baseURL: isCloudflareProvider(model.provider) ? resolveCloudflareBaseUrl(model) : model.baseUrl,
+ baseURL: isCloudflareProvider(model.provider) ? resolveCloudflareBaseUrl(model, env) : model.baseUrl,
dangerouslyAllowBrowser: true,
defaultHeaders,
});
@@ -498,7 +537,7 @@ function buildParams(
context: Context,
options?: OpenAICompletionsOptions,
compat: ResolvedOpenAICompletionsCompat = getCompat(model),
- cacheRetention: CacheRetention = resolveCacheRetention(options?.cacheRetention),
+ cacheRetention: CacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env),
) {
const messages = convertMessages(model, context, compat);
const cacheControl = getCompatCacheControl(compat, cacheRetention);
@@ -554,8 +593,18 @@ function buildParams(
}
if (compat.thinkingFormat === "zai" && model.reasoning) {
- const zaiParams = params as typeof params & { thinking?: { type: "enabled" | "disabled" } };
+ const zaiParams = params as Omit & {
+ thinking?: { type: "enabled" | "disabled" };
+ reasoning_effort?: string;
+ };
zaiParams.thinking = { type: options?.reasoningEffort ? "enabled" : "disabled" };
+ if (options?.reasoningEffort && compat.supportsReasoningEffort) {
+ const mappedEffort = model.thinkingLevelMap?.[options.reasoningEffort];
+ const effort = mappedEffort === undefined ? options.reasoningEffort : mappedEffort;
+ if (typeof effort === "string") {
+ zaiParams.reasoning_effort = effort;
+ }
+ }
} else if (compat.thinkingFormat === "qwen" && model.reasoning) {
(params as any).enable_thinking = !!options?.reasoningEffort;
} else if (compat.thinkingFormat === "qwen-chat-template" && model.reasoning) {
@@ -563,8 +612,17 @@ function buildParams(
enable_thinking: !!options?.reasoningEffort,
preserve_thinking: true,
};
+ } else if (compat.thinkingFormat === "chat-template" && model.reasoning) {
+ const chatTemplateKwargs = buildChatTemplateKwargs(model, options, compat);
+ if (chatTemplateKwargs) {
+ (params as any).chat_template_kwargs = chatTemplateKwargs;
+ }
} else if (compat.thinkingFormat === "deepseek" && model.reasoning) {
- (params as any).thinking = { type: options?.reasoningEffort ? "enabled" : "disabled" };
+ if (options?.reasoningEffort) {
+ (params as any).thinking = { type: "enabled" };
+ } else if (model.thinkingLevelMap?.off !== null) {
+ (params as any).thinking = { type: "disabled" };
+ }
if (options?.reasoningEffort && compat.supportsReasoningEffort) {
(params as any).reasoning_effort =
model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort;
@@ -629,6 +687,44 @@ function buildParams(
return params;
}
+function buildChatTemplateKwargs(
+ model: Model<"openai-completions">,
+ options: OpenAICompletionsOptions | undefined,
+ compat: ResolvedOpenAICompletionsCompat,
+): Record | undefined {
+ const kwargs: Record = {};
+
+ for (const [key, value] of Object.entries(compat.chatTemplateKwargs)) {
+ const resolved = resolveChatTemplateKwargValue(model, options, value);
+ if (resolved !== undefined) {
+ kwargs[key] = resolved;
+ }
+ }
+
+ return Object.keys(kwargs).length > 0 ? kwargs : undefined;
+}
+
+function resolveChatTemplateKwargValue(
+ model: Model<"openai-completions">,
+ options: OpenAICompletionsOptions | undefined,
+ value: ChatTemplateKwargValue,
+): ResolvedChatTemplateKwargValue | undefined {
+ if (typeof value !== "object" || value === null) {
+ return value;
+ }
+
+ const reasoningEffort = options?.reasoningEffort;
+ if (!reasoningEffort && value.omitWhenOff) {
+ return undefined;
+ }
+ if (value.$var === "thinking.enabled") {
+ return !!reasoningEffort;
+ }
+
+ const mappedValue = reasoningEffort ? model.thinkingLevelMap?.[reasoningEffort] : model.thinkingLevelMap?.off;
+ return mappedValue === undefined ? reasoningEffort : typeof mappedValue === "string" ? mappedValue : undefined;
+}
+
function getCompatCacheControl(
compat: ResolvedOpenAICompletionsCompat,
cacheRetention: CacheRetention,
@@ -1141,6 +1237,7 @@ function detectCompat(model: Model<"openai-completions">): ResolvedOpenAIComplet
: "openai",
openRouterRouting: {},
vercelGatewayRouting: {},
+ chatTemplateKwargs: {},
zaiToolStream: false,
supportsStrictMode: !isMoonshot && !isTogether && !isCloudflareAiGateway && !isNvidia,
cacheControlFormat,
@@ -1179,6 +1276,7 @@ function getCompat(model: Model<"openai-completions">): ResolvedOpenAICompletion
thinkingFormat: model.compat.thinkingFormat ?? detected.thinkingFormat,
openRouterRouting: model.compat.openRouterRouting ?? {},
vercelGatewayRouting: model.compat.vercelGatewayRouting ?? detected.vercelGatewayRouting,
+ chatTemplateKwargs: model.compat.chatTemplateKwargs ?? detected.chatTemplateKwargs,
zaiToolStream: model.compat.zaiToolStream ?? detected.zaiToolStream,
supportsStrictMode: model.compat.supportsStrictMode ?? detected.supportsStrictMode,
cacheControlFormat: model.compat.cacheControlFormat ?? detected.cacheControlFormat,
diff --git a/packages/ai/src/api/openai-responses-shared.ts b/packages/ai/src/api/openai-responses-shared.ts
index c006c284..6fd59a44 100644
--- a/packages/ai/src/api/openai-responses-shared.ts
+++ b/packages/ai/src/api/openai-responses-shared.ts
@@ -456,7 +456,8 @@ export async function processResponsesStream(
});
currentBlock = null;
} else if (item.type === "message" && currentBlock?.type === "text") {
- currentBlock.text = item.content.map((c) => (c.type === "output_text" ? c.text : c.refusal)).join("");
+ currentBlock.text =
+ item.content?.map((c) => (c.type === "output_text" ? c.text : c.refusal)).join("") || "";
currentBlock.textSignature = encodeTextSignatureV1(item.id, item.phase ?? undefined);
stream.push({
type: "text_end",
diff --git a/packages/ai/src/api/openai-responses.ts b/packages/ai/src/api/openai-responses.ts
index dfd15866..92756db3 100644
--- a/packages/ai/src/api/openai-responses.ts
+++ b/packages/ai/src/api/openai-responses.ts
@@ -8,6 +8,7 @@ import type {
Context,
Model,
OpenAIResponsesCompat,
+ ProviderEnv,
SimpleStreamOptions,
StreamFunction,
StreamOptions,
@@ -15,6 +16,7 @@ import type {
} from "../types.ts";
import { AssistantMessageEventStream } from "../utils/event-stream.ts";
import { headersToRecord } from "../utils/headers.ts";
+import { getProviderEnvValue } from "../utils/provider-env.ts";
import { isCloudflareProvider, resolveCloudflareBaseUrl } from "./cloudflare.ts";
import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.ts";
import { clampOpenAIPromptCacheKey } from "./openai-prompt-cache.ts";
@@ -27,11 +29,11 @@ const OPENAI_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"
* Resolve cache retention preference.
* Defaults to "short" and uses PI_CACHE_RETENTION for backward compatibility.
*/
-function resolveCacheRetention(cacheRetention?: CacheRetention): CacheRetention {
+function resolveCacheRetention(cacheRetention?: CacheRetention, env?: ProviderEnv): CacheRetention {
if (cacheRetention) {
return cacheRetention;
}
- if (typeof process !== "undefined" && process.env.PI_CACHE_RETENTION === "long") {
+ if (getProviderEnvValue("PI_CACHE_RETENTION", env) === "long") {
return "long";
}
return "short";
@@ -111,9 +113,9 @@ export const stream: StreamFunction<"openai-responses", OpenAIResponsesOptions>
if (!apiKey) {
throw new Error(`No API key for provider: ${model.provider}`);
}
- const cacheRetention = resolveCacheRetention(options?.cacheRetention);
+ const cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);
const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
- const client = createClient(model, context, apiKey, options?.headers, cacheSessionId);
+ const client = createClient(model, context, apiKey, options?.headers, cacheSessionId, options?.env);
let params = buildParams(model, context, options);
const nextParams = await options?.onPayload?.(params, model);
if (nextParams !== undefined) {
@@ -185,6 +187,7 @@ function createClient(
apiKey: string,
optionsHeaders?: Record,
sessionId?: string,
+ env?: ProviderEnv,
) {
const compat = getCompat(model);
const headers = { ...model.headers };
@@ -220,7 +223,7 @@ function createClient(
return new OpenAI({
apiKey,
- baseURL: isCloudflareProvider(model.provider) ? resolveCloudflareBaseUrl(model) : model.baseUrl,
+ baseURL: isCloudflareProvider(model.provider) ? resolveCloudflareBaseUrl(model, env) : model.baseUrl,
dangerouslyAllowBrowser: true,
defaultHeaders,
});
@@ -229,7 +232,7 @@ function createClient(
function buildParams(model: Model<"openai-responses">, context: Context, options?: OpenAIResponsesOptions) {
const messages = convertResponsesMessages(model, context, OPENAI_TOOL_CALL_PROVIDERS);
- const cacheRetention = resolveCacheRetention(options?.cacheRetention);
+ const cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);
const compat = getCompat(model);
const params: ResponseCreateParamsStreaming = {
model: model.id,
diff --git a/packages/ai/src/api/simple-options.ts b/packages/ai/src/api/simple-options.ts
index f1709062..773e8d95 100644
--- a/packages/ai/src/api/simple-options.ts
+++ b/packages/ai/src/api/simple-options.ts
@@ -17,6 +17,7 @@ export function buildBaseOptions(_model: Model, options?: SimpleStreamOptio
maxRetries: options?.maxRetries,
maxRetryDelayMs: options?.maxRetryDelayMs,
metadata: options?.metadata,
+ env: options?.env,
};
}
diff --git a/packages/ai/src/env-api-keys.ts b/packages/ai/src/env-api-keys.ts
index 291d2288..7bd955e1 100644
--- a/packages/ai/src/env-api-keys.ts
+++ b/packages/ai/src/env-api-keys.ts
@@ -23,44 +23,17 @@ if (typeof process !== "undefined" && (process.versions?.node || process.version
});
}
-import type { KnownProvider } from "./types.ts";
-
-let _procEnvCache: Map | null = null;
-
-/**
- * Fallback for https://github.com/oven-sh/bun/issues/27802
- * Bun compiled binaries have an empty `process.env` inside sandbox
- * environments on Linux. We can recover the env from `/proc/self/environ`.
- */
-function getProcEnv(key: string): string | undefined {
- if (!process.versions?.bun) return undefined;
- if (typeof process === "undefined") return undefined;
-
- // If process.env already has entries, the bug is not triggered.
- if (Object.keys(process.env).length > 0) return undefined;
-
- if (_procEnvCache === null) {
- _procEnvCache = new Map();
- try {
- const { readFileSync } = require("node:fs") as typeof import("node:fs");
- const data = readFileSync("/proc/self/environ", "utf-8");
- for (const entry of data.split("\0")) {
- const idx = entry.indexOf("=");
- if (idx > 0) {
- _procEnvCache.set(entry.slice(0, idx), entry.slice(idx + 1));
- }
- }
- } catch {
- // /proc/self/environ may not be readable.
- }
- }
-
- return _procEnvCache.get(key);
-}
+import type { KnownProvider, ProviderEnv } from "./types.ts";
+import { getProviderEnvValue } from "./utils/provider-env.ts";
let cachedVertexAdcCredentialsExists: boolean | null = null;
-function hasVertexAdcCredentials(): boolean {
+function hasVertexAdcCredentials(env?: ProviderEnv): boolean {
+ const explicitCredentialsPath = env?.GOOGLE_APPLICATION_CREDENTIALS;
+ if (explicitCredentialsPath) {
+ return _existsSync ? _existsSync(explicitCredentialsPath) : false;
+ }
+
if (cachedVertexAdcCredentialsExists === null) {
// If node modules haven't loaded yet (async import race at startup),
// return false WITHOUT caching so the next call retries once they're ready.
@@ -75,7 +48,7 @@ function hasVertexAdcCredentials(): boolean {
}
// Check GOOGLE_APPLICATION_CREDENTIALS env var first (standard way)
- const gacPath = process.env.GOOGLE_APPLICATION_CREDENTIALS || getProcEnv("GOOGLE_APPLICATION_CREDENTIALS");
+ const gacPath = getProviderEnvValue("GOOGLE_APPLICATION_CREDENTIALS", env);
if (gacPath) {
cachedVertexAdcCredentialsExists = _existsSync(gacPath);
} else {
@@ -143,13 +116,13 @@ function getApiKeyEnvVars(provider: string): readonly string[] | undefined {
* credential sources such as AWS profiles, AWS IAM credentials, and Google
* Application Default Credentials.
*/
-export function findEnvKeys(provider: KnownProvider): string[] | undefined;
-export function findEnvKeys(provider: string): string[] | undefined;
-export function findEnvKeys(provider: string): string[] | undefined {
+export function findEnvKeys(provider: KnownProvider, env?: ProviderEnv): string[] | undefined;
+export function findEnvKeys(provider: string, env?: ProviderEnv): string[] | undefined;
+export function findEnvKeys(provider: string, env?: ProviderEnv): string[] | undefined {
const envVars = getApiKeyEnvVars(provider);
if (!envVars) return undefined;
- const found = envVars.filter((envVar) => !!process.env[envVar] || !!getProcEnv(envVar));
+ const found = envVars.filter((envVar) => !!getProviderEnvValue(envVar, env));
return found.length > 0 ? found : undefined;
}
@@ -158,25 +131,22 @@ export function findEnvKeys(provider: string): string[] | undefined {
*
* Will not return API keys for providers that require OAuth tokens.
*/
-export function getEnvApiKey(provider: KnownProvider): string | undefined;
-export function getEnvApiKey(provider: string): string | undefined;
-export function getEnvApiKey(provider: string): string | undefined {
- const envKeys = findEnvKeys(provider);
+export function getEnvApiKey(provider: KnownProvider, env?: ProviderEnv): string | undefined;
+export function getEnvApiKey(provider: string, env?: ProviderEnv): string | undefined;
+export function getEnvApiKey(provider: string, env?: ProviderEnv): string | undefined {
+ const envKeys = findEnvKeys(provider, env);
if (envKeys?.[0]) {
- return process.env[envKeys[0]] || getProcEnv(envKeys[0]);
+ return getProviderEnvValue(envKeys[0], env);
}
// Vertex AI supports either an explicit API key or Application Default Credentials.
// Auth is configured via `gcloud auth application-default login`.
if (provider === "google-vertex") {
- const hasCredentials = hasVertexAdcCredentials();
+ const hasCredentials = hasVertexAdcCredentials(env);
const hasProject = !!(
- process.env.GOOGLE_CLOUD_PROJECT ||
- process.env.GCLOUD_PROJECT ||
- getProcEnv("GOOGLE_CLOUD_PROJECT") ||
- getProcEnv("GCLOUD_PROJECT")
+ getProviderEnvValue("GOOGLE_CLOUD_PROJECT", env) || getProviderEnvValue("GCLOUD_PROJECT", env)
);
- const hasLocation = !!(process.env.GOOGLE_CLOUD_LOCATION || getProcEnv("GOOGLE_CLOUD_LOCATION"));
+ const hasLocation = !!getProviderEnvValue("GOOGLE_CLOUD_LOCATION", env);
if (hasCredentials && hasProject && hasLocation) {
return "";
@@ -192,18 +162,12 @@ export function getEnvApiKey(provider: string): string | undefined {
// 5. AWS_CONTAINER_CREDENTIALS_FULL_URI - ECS task roles (full URI)
// 6. AWS_WEB_IDENTITY_TOKEN_FILE - IRSA (IAM Roles for Service Accounts)
if (
- process.env.AWS_PROFILE ||
- (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) ||
- process.env.AWS_BEARER_TOKEN_BEDROCK ||
- process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI ||
- process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI ||
- process.env.AWS_WEB_IDENTITY_TOKEN_FILE ||
- getProcEnv("AWS_PROFILE") ||
- (getProcEnv("AWS_ACCESS_KEY_ID") && getProcEnv("AWS_SECRET_ACCESS_KEY")) ||
- getProcEnv("AWS_BEARER_TOKEN_BEDROCK") ||
- getProcEnv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") ||
- getProcEnv("AWS_CONTAINER_CREDENTIALS_FULL_URI") ||
- getProcEnv("AWS_WEB_IDENTITY_TOKEN_FILE")
+ getProviderEnvValue("AWS_PROFILE", env) ||
+ (getProviderEnvValue("AWS_ACCESS_KEY_ID", env) && getProviderEnvValue("AWS_SECRET_ACCESS_KEY", env)) ||
+ getProviderEnvValue("AWS_BEARER_TOKEN_BEDROCK", env) ||
+ getProviderEnvValue("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", env) ||
+ getProviderEnvValue("AWS_CONTAINER_CREDENTIALS_FULL_URI", env) ||
+ getProviderEnvValue("AWS_WEB_IDENTITY_TOKEN_FILE", env)
) {
return "";
}
diff --git a/packages/ai/src/image-models.generated.ts b/packages/ai/src/image-models.generated.ts
index 09c74180..7545a9f1 100644
--- a/packages/ai/src/image-models.generated.ts
+++ b/packages/ai/src/image-models.generated.ts
@@ -95,6 +95,21 @@ export const IMAGE_MODELS = {
cacheWrite: 0.08333333333333334,
},
} satisfies ImagesModel<"openrouter-images">,
+ "google/gemini-3-pro-image": {
+ id: "google/gemini-3-pro-image",
+ name: "Google: Nano Banana Pro (Gemini 3 Pro Image)",
+ api: "openrouter-images",
+ provider: "openrouter",
+ baseUrl: "https://openrouter.ai/api/v1",
+ input: ["image", "text"],
+ output: ["image", "text"],
+ cost: {
+ input: 2,
+ output: 12,
+ cacheRead: 0.19999999999999998,
+ cacheWrite: 0.375,
+ },
+ } satisfies ImagesModel<"openrouter-images">,
"google/gemini-3-pro-image-preview": {
id: "google/gemini-3-pro-image-preview",
name: "Google: Nano Banana Pro (Gemini 3 Pro Image Preview)",
@@ -110,6 +125,21 @@ export const IMAGE_MODELS = {
cacheWrite: 0.375,
},
} satisfies ImagesModel<"openrouter-images">,
+ "google/gemini-3.1-flash-image": {
+ id: "google/gemini-3.1-flash-image",
+ name: "Google: Nano Banana 2 (Gemini 3.1 Flash Image)",
+ api: "openrouter-images",
+ provider: "openrouter",
+ baseUrl: "https://openrouter.ai/api/v1",
+ input: ["image", "text"],
+ output: ["image", "text"],
+ cost: {
+ input: 0.5,
+ output: 3,
+ cacheRead: 0,
+ cacheWrite: 0,
+ },
+ } satisfies ImagesModel<"openrouter-images">,
"google/gemini-3.1-flash-image-preview": {
id: "google/gemini-3.1-flash-image-preview",
name: "Google: Nano Banana 2 (Gemini 3.1 Flash Image Preview)",
diff --git a/packages/ai/src/models.ts b/packages/ai/src/models.ts
index e540db69..47ed7c8a 100644
--- a/packages/ai/src/models.ts
+++ b/packages/ai/src/models.ts
@@ -372,10 +372,13 @@ export function hasApi(model: Model, api: TApi): model is
}
export function calculateCost(model: Model, usage: Usage): Usage["cost"] {
+ // Anthropic charges 2x base input for 1h cache writes.
+ const longWrite = usage.cacheWrite1h ?? 0;
+ const shortWrite = usage.cacheWrite - longWrite;
usage.cost.input = (model.cost.input / 1000000) * usage.input;
usage.cost.output = (model.cost.output / 1000000) * usage.output;
usage.cost.cacheRead = (model.cost.cacheRead / 1000000) * usage.cacheRead;
- usage.cost.cacheWrite = (model.cost.cacheWrite / 1000000) * usage.cacheWrite;
+ usage.cost.cacheWrite = (model.cost.cacheWrite * shortWrite + model.cost.input * 2 * longWrite) / 1000000;
usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
return usage.cost;
}
diff --git a/packages/ai/src/providers/cerebras.models.ts b/packages/ai/src/providers/cerebras.models.ts
index f074d712..e93c8bc3 100644
--- a/packages/ai/src/providers/cerebras.models.ts
+++ b/packages/ai/src/providers/cerebras.models.ts
@@ -13,30 +13,13 @@ export const CEREBRAS_MODELS = {
reasoning: true,
input: ["text"],
cost: {
- input: 0.25,
- output: 0.69,
+ input: 0.35,
+ output: 0.75,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 131072,
- maxTokens: 32768,
- } satisfies Model<"openai-completions">,
- "llama3.1-8b": {
- id: "llama3.1-8b",
- name: "Llama 3.1 8B",
- api: "openai-completions",
- provider: "cerebras",
- baseUrl: "https://api.cerebras.ai/v1",
- reasoning: false,
- input: ["text"],
- cost: {
- input: 0.1,
- output: 0.1,
- cacheRead: 0,
- cacheWrite: 0,
- },
- contextWindow: 32000,
- maxTokens: 8000,
+ maxTokens: 40960,
} satisfies Model<"openai-completions">,
"zai-glm-4.7": {
id: "zai-glm-4.7",
@@ -44,7 +27,7 @@ export const CEREBRAS_MODELS = {
api: "openai-completions",
provider: "cerebras",
baseUrl: "https://api.cerebras.ai/v1",
- reasoning: false,
+ reasoning: true,
input: ["text"],
cost: {
input: 2.25,
@@ -53,6 +36,6 @@ export const CEREBRAS_MODELS = {
cacheWrite: 0,
},
contextWindow: 131072,
- maxTokens: 40000,
+ maxTokens: 40960,
} satisfies Model<"openai-completions">,
} as const;
diff --git a/packages/ai/src/providers/cloudflare-workers-ai.models.ts b/packages/ai/src/providers/cloudflare-workers-ai.models.ts
index 76cf435d..e3c2ccc6 100644
--- a/packages/ai/src/providers/cloudflare-workers-ai.models.ts
+++ b/packages/ai/src/providers/cloudflare-workers-ai.models.ts
@@ -112,6 +112,24 @@ export const CLOUDFLARE_WORKERS_AI_MODELS = {
contextWindow: 262144,
maxTokens: 256000,
} satisfies Model<"openai-completions">,
+ "@cf/moonshotai/kimi-k2.7-code": {
+ id: "@cf/moonshotai/kimi-k2.7-code",
+ name: "Kimi K2.7 Code",
+ api: "openai-completions",
+ provider: "cloudflare-workers-ai",
+ baseUrl: "https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1",
+ compat: {"sendSessionAffinityHeaders":true},
+ reasoning: true,
+ input: ["text", "image"],
+ cost: {
+ input: 0.95,
+ output: 4,
+ cacheRead: 0.19,
+ cacheWrite: 0,
+ },
+ contextWindow: 262144,
+ maxTokens: 262144,
+ } satisfies Model<"openai-completions">,
"@cf/nvidia/nemotron-3-120b-a12b": {
id: "@cf/nvidia/nemotron-3-120b-a12b",
name: "Nemotron 3 Super 120B",
@@ -202,4 +220,22 @@ export const CLOUDFLARE_WORKERS_AI_MODELS = {
contextWindow: 131072,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
+ "@cf/zai-org/glm-5.2": {
+ id: "@cf/zai-org/glm-5.2",
+ name: "Glm 5.2",
+ api: "openai-completions",
+ provider: "cloudflare-workers-ai",
+ baseUrl: "https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1",
+ compat: {"sendSessionAffinityHeaders":true},
+ reasoning: true,
+ input: ["text"],
+ cost: {
+ input: 1.4,
+ output: 4.4,
+ cacheRead: 0.26,
+ cacheWrite: 0,
+ },
+ contextWindow: 262144,
+ maxTokens: 262144,
+ } satisfies Model<"openai-completions">,
} as const;
diff --git a/packages/ai/src/providers/fireworks.models.ts b/packages/ai/src/providers/fireworks.models.ts
index c515b811..cb93d846 100644
--- a/packages/ai/src/providers/fireworks.models.ts
+++ b/packages/ai/src/providers/fireworks.models.ts
@@ -16,7 +16,7 @@ export const FIREWORKS_MODELS = {
cost: {
input: 0.14,
output: 0.28,
- cacheRead: 0.03,
+ cacheRead: 0.028,
cacheWrite: 0,
},
contextWindow: 1000000,
@@ -58,6 +58,25 @@ export const FIREWORKS_MODELS = {
contextWindow: 202800,
maxTokens: 131072,
} satisfies Model<"anthropic-messages">,
+ "accounts/fireworks/models/glm-5p2": {
+ id: "accounts/fireworks/models/glm-5p2",
+ name: "GLM 5.2",
+ api: "openai-completions",
+ provider: "fireworks",
+ baseUrl: "https://api.fireworks.ai/inference/v1",
+ compat: {"supportsStore":false,"supportsDeveloperRole":false},
+ reasoning: true,
+ thinkingLevelMap: {"off":"none","minimal":null,"low":"high","medium":"high","xhigh":"max"},
+ input: ["text"],
+ cost: {
+ input: 1.4,
+ output: 4.4,
+ cacheRead: 0.26,
+ cacheWrite: 0,
+ },
+ contextWindow: 1048576,
+ maxTokens: 131072,
+ } satisfies Model<"openai-completions">,
"accounts/fireworks/models/gpt-oss-120b": {
id: "accounts/fireworks/models/gpt-oss-120b",
name: "GPT OSS 120B",
@@ -94,24 +113,6 @@ export const FIREWORKS_MODELS = {
contextWindow: 131072,
maxTokens: 32768,
} satisfies Model<"anthropic-messages">,
- "accounts/fireworks/models/kimi-k2p5": {
- id: "accounts/fireworks/models/kimi-k2p5",
- name: "Kimi K2.5",
- api: "anthropic-messages",
- provider: "fireworks",
- baseUrl: "https://api.fireworks.ai/inference",
- compat: {"sendSessionAffinityHeaders":true,"supportsEagerToolInputStreaming":false,"supportsCacheControlOnTools":false,"supportsLongCacheRetention":false},
- reasoning: true,
- input: ["text", "image"],
- cost: {
- input: 0.6,
- output: 3,
- cacheRead: 0.1,
- cacheWrite: 0,
- },
- contextWindow: 256000,
- maxTokens: 256000,
- } satisfies Model<"anthropic-messages">,
"accounts/fireworks/models/kimi-k2p6": {
id: "accounts/fireworks/models/kimi-k2p6",
name: "Kimi K2.6",
@@ -130,23 +131,23 @@ export const FIREWORKS_MODELS = {
contextWindow: 262000,
maxTokens: 262000,
} satisfies Model<"anthropic-messages">,
- "accounts/fireworks/models/minimax-m2p5": {
- id: "accounts/fireworks/models/minimax-m2p5",
- name: "MiniMax-M2.5",
+ "accounts/fireworks/models/kimi-k2p7-code": {
+ id: "accounts/fireworks/models/kimi-k2p7-code",
+ name: "Kimi K2.7 Code",
api: "anthropic-messages",
provider: "fireworks",
baseUrl: "https://api.fireworks.ai/inference",
compat: {"sendSessionAffinityHeaders":true,"supportsEagerToolInputStreaming":false,"supportsCacheControlOnTools":false,"supportsLongCacheRetention":false},
reasoning: true,
- input: ["text"],
+ input: ["text", "image"],
cost: {
- input: 0.3,
- output: 1.2,
- cacheRead: 0.03,
+ input: 0.95,
+ output: 4,
+ cacheRead: 0.19,
cacheWrite: 0,
},
- contextWindow: 196608,
- maxTokens: 196608,
+ contextWindow: 262000,
+ maxTokens: 262000,
} satisfies Model<"anthropic-messages">,
"accounts/fireworks/models/minimax-m2p7": {
id: "accounts/fireworks/models/minimax-m2p7",
@@ -166,9 +167,27 @@ export const FIREWORKS_MODELS = {
contextWindow: 196608,
maxTokens: 196608,
} satisfies Model<"anthropic-messages">,
- "accounts/fireworks/models/qwen3p6-plus": {
- id: "accounts/fireworks/models/qwen3p6-plus",
- name: "Qwen 3.6 Plus",
+ "accounts/fireworks/models/minimax-m3": {
+ id: "accounts/fireworks/models/minimax-m3",
+ name: "MiniMax-M3",
+ api: "anthropic-messages",
+ provider: "fireworks",
+ baseUrl: "https://api.fireworks.ai/inference",
+ compat: {"sendSessionAffinityHeaders":true,"supportsEagerToolInputStreaming":false,"supportsCacheControlOnTools":false,"supportsLongCacheRetention":false},
+ reasoning: true,
+ input: ["text"],
+ cost: {
+ input: 0.3,
+ output: 1.2,
+ cacheRead: 0.06,
+ cacheWrite: 0,
+ },
+ contextWindow: 512000,
+ maxTokens: 512000,
+ } satisfies Model<"anthropic-messages">,
+ "accounts/fireworks/models/qwen3p7-plus": {
+ id: "accounts/fireworks/models/qwen3p7-plus",
+ name: "Qwen 3.7 Plus",
api: "anthropic-messages",
provider: "fireworks",
baseUrl: "https://api.fireworks.ai/inference",
@@ -176,9 +195,9 @@ export const FIREWORKS_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.5,
- output: 3,
- cacheRead: 0.1,
+ input: 0.4,
+ output: 1.6,
+ cacheRead: 0.08,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -238,4 +257,22 @@ export const FIREWORKS_MODELS = {
contextWindow: 262000,
maxTokens: 262000,
} satisfies Model<"anthropic-messages">,
+ "accounts/fireworks/routers/kimi-k2p7-code-fast": {
+ id: "accounts/fireworks/routers/kimi-k2p7-code-fast",
+ name: "Kimi K2.7 Code Fast",
+ api: "anthropic-messages",
+ provider: "fireworks",
+ baseUrl: "https://api.fireworks.ai/inference",
+ compat: {"sendSessionAffinityHeaders":true,"supportsEagerToolInputStreaming":false,"supportsCacheControlOnTools":false,"supportsLongCacheRetention":false},
+ reasoning: true,
+ input: ["text", "image"],
+ cost: {
+ input: 1.9,
+ output: 8,
+ cacheRead: 0.38,
+ cacheWrite: 0,
+ },
+ contextWindow: 262000,
+ maxTokens: 262000,
+ } satisfies Model<"anthropic-messages">,
} as const;
diff --git a/packages/ai/src/providers/fireworks.ts b/packages/ai/src/providers/fireworks.ts
index 9c0b5091..518fb259 100644
--- a/packages/ai/src/providers/fireworks.ts
+++ b/packages/ai/src/providers/fireworks.ts
@@ -1,15 +1,19 @@
import { anthropicMessagesApi } from "../api/anthropic-messages.lazy.ts";
+import { openAICompletionsApi } from "../api/openai-completions.lazy.ts";
import { envApiKeyAuth } from "../auth/helpers.ts";
import { createProvider, type Provider } from "../models.ts";
import { FIREWORKS_MODELS } from "./fireworks.models.ts";
-export function fireworksProvider(): Provider<"anthropic-messages"> {
+export function fireworksProvider(): Provider<"anthropic-messages" | "openai-completions"> {
return createProvider({
id: "fireworks",
name: "Fireworks",
baseUrl: "https://api.fireworks.ai/inference",
auth: { apiKey: envApiKeyAuth("Fireworks API key", ["FIREWORKS_API_KEY"]) },
models: Object.values(FIREWORKS_MODELS),
- api: anthropicMessagesApi(),
+ api: {
+ "anthropic-messages": anthropicMessagesApi(),
+ "openai-completions": openAICompletionsApi(),
+ },
});
}
diff --git a/packages/ai/src/providers/github-copilot.models.ts b/packages/ai/src/providers/github-copilot.models.ts
index 471a0b95..cf866ec2 100644
--- a/packages/ai/src/providers/github-copilot.models.ts
+++ b/packages/ai/src/providers/github-copilot.models.ts
@@ -4,6 +4,25 @@
import type { Model } from "../types.ts";
export const GITHUB_COPILOT_MODELS = {
+ "claude-fable-5": {
+ id: "claude-fable-5",
+ name: "Claude Fable 5",
+ api: "openai-completions",
+ provider: "github-copilot",
+ baseUrl: "https://api.individual.githubcopilot.com",
+ headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"},
+ compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false},
+ reasoning: true,
+ input: ["text", "image"],
+ cost: {
+ input: 10,
+ output: 50,
+ cacheRead: 1,
+ cacheWrite: 12.5,
+ },
+ contextWindow: 1000000,
+ maxTokens: 128000,
+ } satisfies Model<"openai-completions">,
"claude-haiku-4.5": {
id: "claude-haiku-4.5",
name: "Claude Haiku 4.5 (latest)",
@@ -70,7 +89,7 @@ export const GITHUB_COPILOT_MODELS = {
headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"},
compat: {"forceAdaptiveThinking":true,"supportsTemperature":false},
reasoning: true,
- thinkingLevelMap: {"xhigh":"xhigh"},
+ thinkingLevelMap: {"xhigh":"xhigh","minimal":"low"},
input: ["text", "image"],
cost: {
input: 5,
@@ -90,7 +109,7 @@ export const GITHUB_COPILOT_MODELS = {
headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"},
compat: {"forceAdaptiveThinking":true,"supportsTemperature":false},
reasoning: true,
- thinkingLevelMap: {"xhigh":"xhigh"},
+ thinkingLevelMap: {"xhigh":"xhigh","minimal":"low"},
input: ["text", "image"],
cost: {
input: 5,
@@ -148,6 +167,7 @@ export const GITHUB_COPILOT_MODELS = {
headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"},
compat: {"forceAdaptiveThinking":true},
reasoning: true,
+ thinkingLevelMap: {"minimal":"low","xhigh":"max"},
input: ["text", "image"],
cost: {
input: 3,
@@ -405,23 +425,4 @@ export const GITHUB_COPILOT_MODELS = {
contextWindow: 400000,
maxTokens: 128000,
} satisfies Model<"openai-responses">,
- "raptor-mini": {
- id: "raptor-mini",
- name: "Raptor mini",
- api: "openai-completions",
- provider: "github-copilot",
- baseUrl: "https://api.individual.githubcopilot.com",
- headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"},
- compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false},
- reasoning: true,
- input: ["text", "image"],
- cost: {
- input: 0.25,
- output: 2,
- cacheRead: 0.025,
- cacheWrite: 0,
- },
- contextWindow: 400000,
- maxTokens: 128000,
- } satisfies Model<"openai-completions">,
} as const;
diff --git a/packages/ai/src/providers/google-vertex.models.ts b/packages/ai/src/providers/google-vertex.models.ts
index 8e72a237..8dfa2414 100644
--- a/packages/ai/src/providers/google-vertex.models.ts
+++ b/packages/ai/src/providers/google-vertex.models.ts
@@ -4,94 +4,9 @@
import type { Model } from "../types.ts";
export const GOOGLE_VERTEX_MODELS = {
- "gemini-1.5-flash": {
- id: "gemini-1.5-flash",
- name: "Gemini 1.5 Flash (Vertex)",
- api: "google-vertex",
- provider: "google-vertex",
- baseUrl: "https://{location}-aiplatform.googleapis.com",
- reasoning: false,
- input: ["text", "image"],
- cost: {
- input: 0.075,
- output: 0.3,
- cacheRead: 0.01875,
- cacheWrite: 0,
- },
- contextWindow: 1000000,
- maxTokens: 8192,
- } satisfies Model<"google-vertex">,
- "gemini-1.5-flash-8b": {
- id: "gemini-1.5-flash-8b",
- name: "Gemini 1.5 Flash-8B (Vertex)",
- api: "google-vertex",
- provider: "google-vertex",
- baseUrl: "https://{location}-aiplatform.googleapis.com",
- reasoning: false,
- input: ["text", "image"],
- cost: {
- input: 0.0375,
- output: 0.15,
- cacheRead: 0.01,
- cacheWrite: 0,
- },
- contextWindow: 1000000,
- maxTokens: 8192,
- } satisfies Model<"google-vertex">,
- "gemini-1.5-pro": {
- id: "gemini-1.5-pro",
- name: "Gemini 1.5 Pro (Vertex)",
- api: "google-vertex",
- provider: "google-vertex",
- baseUrl: "https://{location}-aiplatform.googleapis.com",
- reasoning: false,
- input: ["text", "image"],
- cost: {
- input: 1.25,
- output: 5,
- cacheRead: 0.3125,
- cacheWrite: 0,
- },
- contextWindow: 1000000,
- maxTokens: 8192,
- } satisfies Model<"google-vertex">,
- "gemini-2.0-flash": {
- id: "gemini-2.0-flash",
- name: "Gemini 2.0 Flash (Vertex)",
- api: "google-vertex",
- provider: "google-vertex",
- baseUrl: "https://{location}-aiplatform.googleapis.com",
- reasoning: false,
- input: ["text", "image"],
- cost: {
- input: 0.15,
- output: 0.6,
- cacheRead: 0.0375,
- cacheWrite: 0,
- },
- contextWindow: 1048576,
- maxTokens: 8192,
- } satisfies Model<"google-vertex">,
- "gemini-2.0-flash-lite": {
- id: "gemini-2.0-flash-lite",
- name: "Gemini 2.0 Flash Lite (Vertex)",
- api: "google-vertex",
- provider: "google-vertex",
- baseUrl: "https://{location}-aiplatform.googleapis.com",
- reasoning: true,
- input: ["text", "image"],
- cost: {
- input: 0.075,
- output: 0.3,
- cacheRead: 0.01875,
- cacheWrite: 0,
- },
- contextWindow: 1048576,
- maxTokens: 65536,
- } satisfies Model<"google-vertex">,
"gemini-2.5-flash": {
id: "gemini-2.5-flash",
- name: "Gemini 2.5 Flash (Vertex)",
+ name: "Gemini 2.5 Flash",
api: "google-vertex",
provider: "google-vertex",
baseUrl: "https://{location}-aiplatform.googleapis.com",
@@ -108,24 +23,7 @@ export const GOOGLE_VERTEX_MODELS = {
} satisfies Model<"google-vertex">,
"gemini-2.5-flash-lite": {
id: "gemini-2.5-flash-lite",
- name: "Gemini 2.5 Flash Lite (Vertex)",
- api: "google-vertex",
- provider: "google-vertex",
- baseUrl: "https://{location}-aiplatform.googleapis.com",
- reasoning: true,
- input: ["text", "image"],
- cost: {
- input: 0.1,
- output: 0.4,
- cacheRead: 0.01,
- cacheWrite: 0,
- },
- contextWindow: 1048576,
- maxTokens: 65536,
- } satisfies Model<"google-vertex">,
- "gemini-2.5-flash-lite-preview-09-2025": {
- id: "gemini-2.5-flash-lite-preview-09-2025",
- name: "Gemini 2.5 Flash Lite Preview 09-25 (Vertex)",
+ name: "Gemini 2.5 Flash-Lite",
api: "google-vertex",
provider: "google-vertex",
baseUrl: "https://{location}-aiplatform.googleapis.com",
@@ -142,7 +40,7 @@ export const GOOGLE_VERTEX_MODELS = {
} satisfies Model<"google-vertex">,
"gemini-2.5-pro": {
id: "gemini-2.5-pro",
- name: "Gemini 2.5 Pro (Vertex)",
+ name: "Gemini 2.5 Pro",
api: "google-vertex",
provider: "google-vertex",
baseUrl: "https://{location}-aiplatform.googleapis.com",
@@ -159,7 +57,7 @@ export const GOOGLE_VERTEX_MODELS = {
} satisfies Model<"google-vertex">,
"gemini-3-flash-preview": {
id: "gemini-3-flash-preview",
- name: "Gemini 3 Flash Preview (Vertex)",
+ name: "Gemini 3 Flash Preview",
api: "google-vertex",
provider: "google-vertex",
baseUrl: "https://{location}-aiplatform.googleapis.com",
@@ -175,27 +73,27 @@ export const GOOGLE_VERTEX_MODELS = {
contextWindow: 1048576,
maxTokens: 65536,
} satisfies Model<"google-vertex">,
- "gemini-3-pro-preview": {
- id: "gemini-3-pro-preview",
- name: "Gemini 3 Pro Preview (Vertex)",
+ "gemini-3.1-flash-lite": {
+ id: "gemini-3.1-flash-lite",
+ name: "Gemini 3.1 Flash Lite",
api: "google-vertex",
provider: "google-vertex",
baseUrl: "https://{location}-aiplatform.googleapis.com",
reasoning: true,
- thinkingLevelMap: {"off":null,"minimal":null,"low":"LOW","medium":null,"high":"HIGH"},
+ thinkingLevelMap: {"off":null},
input: ["text", "image"],
cost: {
- input: 2,
- output: 12,
- cacheRead: 0.2,
+ input: 0.25,
+ output: 1.5,
+ cacheRead: 0.025,
cacheWrite: 0,
},
- contextWindow: 1000000,
- maxTokens: 64000,
+ contextWindow: 1048576,
+ maxTokens: 65536,
} satisfies Model<"google-vertex">,
"gemini-3.1-pro-preview": {
id: "gemini-3.1-pro-preview",
- name: "Gemini 3.1 Pro Preview (Vertex)",
+ name: "Gemini 3.1 Pro Preview",
api: "google-vertex",
provider: "google-vertex",
baseUrl: "https://{location}-aiplatform.googleapis.com",
@@ -213,7 +111,7 @@ export const GOOGLE_VERTEX_MODELS = {
} satisfies Model<"google-vertex">,
"gemini-3.1-pro-preview-customtools": {
id: "gemini-3.1-pro-preview-customtools",
- name: "Gemini 3.1 Pro Preview Custom Tools (Vertex)",
+ name: "Gemini 3.1 Pro Preview Custom Tools",
api: "google-vertex",
provider: "google-vertex",
baseUrl: "https://{location}-aiplatform.googleapis.com",
@@ -229,4 +127,58 @@ export const GOOGLE_VERTEX_MODELS = {
contextWindow: 1048576,
maxTokens: 65536,
} satisfies Model<"google-vertex">,
+ "gemini-3.5-flash": {
+ id: "gemini-3.5-flash",
+ name: "Gemini 3.5 Flash",
+ api: "google-vertex",
+ provider: "google-vertex",
+ baseUrl: "https://{location}-aiplatform.googleapis.com",
+ reasoning: true,
+ thinkingLevelMap: {"off":null},
+ input: ["text", "image"],
+ cost: {
+ input: 1.5,
+ output: 9,
+ cacheRead: 0.15,
+ cacheWrite: 0,
+ },
+ contextWindow: 1048576,
+ maxTokens: 65536,
+ } satisfies Model<"google-vertex">,
+ "gemini-flash-latest": {
+ id: "gemini-flash-latest",
+ name: "Gemini Flash Latest",
+ api: "google-vertex",
+ provider: "google-vertex",
+ baseUrl: "https://{location}-aiplatform.googleapis.com",
+ reasoning: true,
+ thinkingLevelMap: {"off":null},
+ input: ["text", "image"],
+ cost: {
+ input: 1.5,
+ output: 9,
+ cacheRead: 0.15,
+ cacheWrite: 0,
+ },
+ contextWindow: 1048576,
+ maxTokens: 65536,
+ } satisfies Model<"google-vertex">,
+ "gemini-flash-lite-latest": {
+ id: "gemini-flash-lite-latest",
+ name: "Gemini Flash-Lite Latest",
+ api: "google-vertex",
+ provider: "google-vertex",
+ baseUrl: "https://{location}-aiplatform.googleapis.com",
+ reasoning: true,
+ thinkingLevelMap: {"off":null},
+ input: ["text", "image"],
+ cost: {
+ input: 0.25,
+ output: 1.5,
+ cacheRead: 0.025,
+ cacheWrite: 0,
+ },
+ contextWindow: 1048576,
+ maxTokens: 65536,
+ } satisfies Model<"google-vertex">,
} as const;
diff --git a/packages/ai/src/providers/google.models.ts b/packages/ai/src/providers/google.models.ts
index 7a6b5d38..334e3b43 100644
--- a/packages/ai/src/providers/google.models.ts
+++ b/packages/ai/src/providers/google.models.ts
@@ -222,11 +222,12 @@ export const GOOGLE_MODELS = {
provider: "google",
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
reasoning: true,
+ thinkingLevelMap: {"off":null},
input: ["text", "image"],
cost: {
- input: 0.3,
- output: 2.5,
- cacheRead: 0.075,
+ input: 1.5,
+ output: 9,
+ cacheRead: 0.15,
cacheWrite: 0,
},
contextWindow: 1048576,
@@ -239,10 +240,11 @@ export const GOOGLE_MODELS = {
provider: "google",
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
reasoning: true,
+ thinkingLevelMap: {"off":null},
input: ["text", "image"],
cost: {
- input: 0.1,
- output: 0.4,
+ input: 0.25,
+ output: 1.5,
cacheRead: 0.025,
cacheWrite: 0,
},
diff --git a/packages/ai/src/providers/kimi-coding.models.ts b/packages/ai/src/providers/kimi-coding.models.ts
index dd6f444c..a3b1f266 100644
--- a/packages/ai/src/providers/kimi-coding.models.ts
+++ b/packages/ai/src/providers/kimi-coding.models.ts
@@ -4,6 +4,24 @@
import type { Model } from "../types.ts";
export const KIMI_CODING_MODELS = {
+ "k2p7": {
+ id: "k2p7",
+ name: "Kimi K2.7 Code",
+ api: "anthropic-messages",
+ provider: "kimi-coding",
+ baseUrl: "https://api.kimi.com/coding",
+ headers: {"User-Agent":"KimiCLI/1.5"},
+ reasoning: true,
+ input: ["text", "image"],
+ cost: {
+ input: 0,
+ output: 0,
+ cacheRead: 0,
+ cacheWrite: 0,
+ },
+ contextWindow: 262144,
+ maxTokens: 32768,
+ } satisfies Model<"anthropic-messages">,
"kimi-for-coding": {
id: "kimi-for-coding",
name: "Kimi For Coding",
diff --git a/packages/ai/src/providers/mistral.models.ts b/packages/ai/src/providers/mistral.models.ts
index 689a092d..7060772b 100644
--- a/packages/ai/src/providers/mistral.models.ts
+++ b/packages/ai/src/providers/mistral.models.ts
@@ -15,7 +15,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.3,
output: 0.9,
- cacheRead: 0,
+ cacheRead: 0.03,
cacheWrite: 0,
},
contextWindow: 256000,
@@ -32,7 +32,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.4,
output: 2,
- cacheRead: 0,
+ cacheRead: 0.04,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -49,7 +49,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.4,
output: 2,
- cacheRead: 0,
+ cacheRead: 0.04,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -66,7 +66,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.4,
output: 2,
- cacheRead: 0,
+ cacheRead: 0.04,
cacheWrite: 0,
},
contextWindow: 128000,
@@ -83,7 +83,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.4,
output: 2,
- cacheRead: 0,
+ cacheRead: 0.04,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -100,7 +100,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.1,
output: 0.3,
- cacheRead: 0,
+ cacheRead: 0.01,
cacheWrite: 0,
},
contextWindow: 128000,
@@ -117,7 +117,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.1,
output: 0.3,
- cacheRead: 0,
+ cacheRead: 0.01,
cacheWrite: 0,
},
contextWindow: 128000,
@@ -151,7 +151,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 2,
output: 5,
- cacheRead: 0,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 128000,
@@ -168,7 +168,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.5,
output: 1.5,
- cacheRead: 0,
+ cacheRead: 0.05,
cacheWrite: 0,
},
contextWindow: 128000,
@@ -185,7 +185,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.04,
output: 0.04,
- cacheRead: 0,
+ cacheRead: 0.004,
cacheWrite: 0,
},
contextWindow: 128000,
@@ -202,7 +202,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.1,
output: 0.1,
- cacheRead: 0,
+ cacheRead: 0.01,
cacheWrite: 0,
},
contextWindow: 128000,
@@ -219,7 +219,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 2,
output: 6,
- cacheRead: 0,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 131072,
@@ -236,7 +236,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.5,
output: 1.5,
- cacheRead: 0,
+ cacheRead: 0.05,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -253,7 +253,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.5,
output: 1.5,
- cacheRead: 0,
+ cacheRead: 0.05,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -270,7 +270,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.4,
output: 2,
- cacheRead: 0,
+ cacheRead: 0.04,
cacheWrite: 0,
},
contextWindow: 131072,
@@ -287,7 +287,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.4,
output: 2,
- cacheRead: 0,
+ cacheRead: 0.04,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -304,7 +304,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 1.5,
output: 7.5,
- cacheRead: 0,
+ cacheRead: 0.15,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -338,7 +338,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.4,
output: 2,
- cacheRead: 0,
+ cacheRead: 0.04,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -355,7 +355,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.15,
output: 0.15,
- cacheRead: 0,
+ cacheRead: 0.015,
cacheWrite: 0,
},
contextWindow: 128000,
@@ -372,7 +372,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.1,
output: 0.3,
- cacheRead: 0,
+ cacheRead: 0.01,
cacheWrite: 0,
},
contextWindow: 128000,
@@ -389,7 +389,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.15,
output: 0.6,
- cacheRead: 0,
+ cacheRead: 0.015,
cacheWrite: 0,
},
contextWindow: 256000,
@@ -406,7 +406,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.15,
output: 0.6,
- cacheRead: 0,
+ cacheRead: 0.015,
cacheWrite: 0,
},
contextWindow: 256000,
@@ -423,7 +423,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.25,
output: 0.25,
- cacheRead: 0,
+ cacheRead: 0.025,
cacheWrite: 0,
},
contextWindow: 8000,
@@ -440,7 +440,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.15,
output: 0.15,
- cacheRead: 0,
+ cacheRead: 0.015,
cacheWrite: 0,
},
contextWindow: 128000,
@@ -457,7 +457,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 2,
output: 6,
- cacheRead: 0,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 64000,
@@ -474,7 +474,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.7,
output: 0.7,
- cacheRead: 0,
+ cacheRead: 0.07,
cacheWrite: 0,
},
contextWindow: 32000,
@@ -491,7 +491,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 0.15,
output: 0.15,
- cacheRead: 0,
+ cacheRead: 0.015,
cacheWrite: 0,
},
contextWindow: 128000,
@@ -508,7 +508,7 @@ export const MISTRAL_MODELS = {
cost: {
input: 2,
output: 6,
- cacheRead: 0,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 128000,
diff --git a/packages/ai/src/providers/moonshotai-cn.models.ts b/packages/ai/src/providers/moonshotai-cn.models.ts
index ea2c9e7a..899f9b11 100644
--- a/packages/ai/src/providers/moonshotai-cn.models.ts
+++ b/packages/ai/src/providers/moonshotai-cn.models.ts
@@ -130,4 +130,42 @@ export const MOONSHOTAI_CN_MODELS = {
contextWindow: 262144,
maxTokens: 262144,
} satisfies Model<"openai-completions">,
+ "kimi-k2.7-code": {
+ id: "kimi-k2.7-code",
+ name: "Kimi K2.7 Code",
+ api: "openai-completions",
+ provider: "moonshotai-cn",
+ baseUrl: "https://api.moonshot.cn/v1",
+ compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"thinkingFormat":"deepseek"},
+ reasoning: true,
+ thinkingLevelMap: {"off":null},
+ input: ["text", "image"],
+ cost: {
+ input: 0.95,
+ output: 4,
+ cacheRead: 0.19,
+ cacheWrite: 0,
+ },
+ contextWindow: 262144,
+ maxTokens: 262144,
+ } satisfies Model<"openai-completions">,
+ "kimi-k2.7-code-highspeed": {
+ id: "kimi-k2.7-code-highspeed",
+ name: "Kimi K2.7 Code HighSpeed",
+ api: "openai-completions",
+ provider: "moonshotai-cn",
+ baseUrl: "https://api.moonshot.cn/v1",
+ compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"thinkingFormat":"deepseek"},
+ reasoning: true,
+ thinkingLevelMap: {"off":null},
+ input: ["text", "image"],
+ cost: {
+ input: 1.9,
+ output: 8,
+ cacheRead: 0.38,
+ cacheWrite: 0,
+ },
+ contextWindow: 262144,
+ maxTokens: 262144,
+ } satisfies Model<"openai-completions">,
} as const;
diff --git a/packages/ai/src/providers/moonshotai.models.ts b/packages/ai/src/providers/moonshotai.models.ts
index b04a7bcb..2ec685e6 100644
--- a/packages/ai/src/providers/moonshotai.models.ts
+++ b/packages/ai/src/providers/moonshotai.models.ts
@@ -130,4 +130,42 @@ export const MOONSHOTAI_MODELS = {
contextWindow: 262144,
maxTokens: 262144,
} satisfies Model<"openai-completions">,
+ "kimi-k2.7-code": {
+ id: "kimi-k2.7-code",
+ name: "Kimi K2.7 Code",
+ api: "openai-completions",
+ provider: "moonshotai",
+ baseUrl: "https://api.moonshot.ai/v1",
+ compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"thinkingFormat":"deepseek"},
+ reasoning: true,
+ thinkingLevelMap: {"off":null},
+ input: ["text", "image"],
+ cost: {
+ input: 0.95,
+ output: 4,
+ cacheRead: 0.19,
+ cacheWrite: 0,
+ },
+ contextWindow: 262144,
+ maxTokens: 262144,
+ } satisfies Model<"openai-completions">,
+ "kimi-k2.7-code-highspeed": {
+ id: "kimi-k2.7-code-highspeed",
+ name: "Kimi K2.7 Code HighSpeed",
+ api: "openai-completions",
+ provider: "moonshotai",
+ baseUrl: "https://api.moonshot.ai/v1",
+ compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"thinkingFormat":"deepseek"},
+ reasoning: true,
+ thinkingLevelMap: {"off":null},
+ input: ["text", "image"],
+ cost: {
+ input: 1.9,
+ output: 8,
+ cacheRead: 0.38,
+ cacheWrite: 0,
+ },
+ contextWindow: 262144,
+ maxTokens: 262144,
+ } satisfies Model<"openai-completions">,
} as const;
diff --git a/packages/ai/src/providers/nvidia.models.ts b/packages/ai/src/providers/nvidia.models.ts
index 76590901..d0a0c713 100644
--- a/packages/ai/src/providers/nvidia.models.ts
+++ b/packages/ai/src/providers/nvidia.models.ts
@@ -289,25 +289,6 @@ export const NVIDIA_MODELS = {
contextWindow: 131072,
maxTokens: 32768,
} satisfies Model<"openai-completions">,
- "qwen/qwen3-coder-480b-a35b-instruct": {
- id: "qwen/qwen3-coder-480b-a35b-instruct",
- name: "Qwen3 Coder 480B A35B Instruct",
- api: "openai-completions",
- provider: "nvidia",
- baseUrl: "https://integrate.api.nvidia.com/v1",
- headers: {"NVCF-POLL-SECONDS":"3600"},
- compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"supportsLongCacheRetention":false},
- reasoning: false,
- input: ["text"],
- cost: {
- input: 0,
- output: 0,
- cacheRead: 0,
- cacheWrite: 0,
- },
- contextWindow: 262144,
- maxTokens: 66536,
- } satisfies Model<"openai-completions">,
"qwen/qwen3.5-122b-a10b": {
id: "qwen/qwen3.5-122b-a10b",
name: "Qwen3.5 122B-A10B",
diff --git a/packages/ai/src/providers/opencode-go.models.ts b/packages/ai/src/providers/opencode-go.models.ts
index 7dcb726f..d58e50b7 100644
--- a/packages/ai/src/providers/opencode-go.models.ts
+++ b/packages/ai/src/providers/opencode-go.models.ts
@@ -42,24 +42,6 @@ export const OPENCODE_GO_MODELS = {
contextWindow: 1000000,
maxTokens: 384000,
} satisfies Model<"openai-completions">,
- "glm-5": {
- id: "glm-5",
- name: "GLM-5",
- api: "openai-completions",
- provider: "opencode-go",
- baseUrl: "https://opencode.ai/zen/go/v1",
- compat: {"maxTokensField":"max_tokens"},
- reasoning: true,
- input: ["text"],
- cost: {
- input: 1,
- output: 3.2,
- cacheRead: 0.2,
- cacheWrite: 0,
- },
- contextWindow: 202752,
- maxTokens: 32768,
- } satisfies Model<"openai-completions">,
"glm-5.1": {
id: "glm-5.1",
name: "GLM-5.1",
@@ -78,23 +60,23 @@ export const OPENCODE_GO_MODELS = {
contextWindow: 202752,
maxTokens: 32768,
} satisfies Model<"openai-completions">,
- "kimi-k2.5": {
- id: "kimi-k2.5",
- name: "Kimi K2.5",
+ "glm-5.2": {
+ id: "glm-5.2",
+ name: "GLM-5.2",
api: "openai-completions",
provider: "opencode-go",
baseUrl: "https://opencode.ai/zen/go/v1",
compat: {"maxTokensField":"max_tokens"},
reasoning: true,
- input: ["text", "image"],
+ input: ["text"],
cost: {
- input: 0.6,
- output: 3,
- cacheRead: 0.1,
+ input: 1.4,
+ output: 4.4,
+ cacheRead: 0.26,
cacheWrite: 0,
},
- contextWindow: 262144,
- maxTokens: 65536,
+ contextWindow: 1000000,
+ maxTokens: 131072,
} satisfies Model<"openai-completions">,
"kimi-k2.6": {
id: "kimi-k2.6",
@@ -102,7 +84,7 @@ export const OPENCODE_GO_MODELS = {
api: "openai-completions",
provider: "opencode-go",
baseUrl: "https://opencode.ai/zen/go/v1",
- compat: {"thinkingFormat":"deepseek","supportsReasoningEffort":false,"maxTokensField":"max_tokens"},
+ compat: {"thinkingFormat":"deepseek","supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsLongCacheRetention":false},
reasoning: true,
thinkingLevelMap: {"minimal":null,"low":null,"medium":null},
input: ["text", "image"],
@@ -115,6 +97,24 @@ export const OPENCODE_GO_MODELS = {
contextWindow: 262144,
maxTokens: 65536,
} satisfies Model<"openai-completions">,
+ "kimi-k2.7-code": {
+ id: "kimi-k2.7-code",
+ name: "Kimi K2.7 Code",
+ api: "openai-completions",
+ provider: "opencode-go",
+ baseUrl: "https://opencode.ai/zen/go/v1",
+ compat: {"maxTokensField":"max_tokens"},
+ reasoning: true,
+ input: ["text", "image"],
+ cost: {
+ input: 0.95,
+ output: 4,
+ cacheRead: 0.19,
+ cacheWrite: 0,
+ },
+ contextWindow: 262144,
+ maxTokens: 262144,
+ } satisfies Model<"openai-completions">,
"mimo-v2.5": {
id: "mimo-v2.5",
name: "MiMo V2.5",
@@ -151,23 +151,6 @@ export const OPENCODE_GO_MODELS = {
contextWindow: 1048576,
maxTokens: 128000,
} satisfies Model<"openai-completions">,
- "minimax-m2.5": {
- id: "minimax-m2.5",
- name: "MiniMax M2.5",
- api: "anthropic-messages",
- provider: "opencode-go",
- baseUrl: "https://opencode.ai/zen/go",
- reasoning: true,
- input: ["text"],
- cost: {
- input: 0.3,
- output: 1.2,
- cacheRead: 0.03,
- cacheWrite: 0,
- },
- contextWindow: 204800,
- maxTokens: 65536,
- } satisfies Model<"anthropic-messages">,
"minimax-m2.7": {
id: "minimax-m2.7",
name: "MiniMax M2.7",
@@ -188,16 +171,16 @@ export const OPENCODE_GO_MODELS = {
} satisfies Model<"openai-completions">,
"minimax-m3": {
id: "minimax-m3",
- name: "MiniMax M3",
+ name: "MiniMax M3 (3x usage)",
api: "anthropic-messages",
provider: "opencode-go",
baseUrl: "https://opencode.ai/zen/go",
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.3,
- output: 1.2,
- cacheRead: 0.06,
+ input: 0.1,
+ output: 0.4,
+ cacheRead: 0.02,
cacheWrite: 0,
},
contextWindow: 512000,
diff --git a/packages/ai/src/providers/opencode.models.ts b/packages/ai/src/providers/opencode.models.ts
index 6138758d..1b050cf8 100644
--- a/packages/ai/src/providers/opencode.models.ts
+++ b/packages/ai/src/providers/opencode.models.ts
@@ -22,25 +22,6 @@ export const OPENCODE_MODELS = {
contextWindow: 200000,
maxTokens: 32000,
} satisfies Model<"openai-completions">,
- "claude-fable-5": {
- id: "claude-fable-5",
- name: "Claude Fable 5",
- api: "anthropic-messages",
- provider: "opencode",
- baseUrl: "https://opencode.ai/zen",
- compat: {"forceAdaptiveThinking":true},
- reasoning: true,
- thinkingLevelMap: {"off":null,"xhigh":"xhigh"},
- input: ["text", "image"],
- cost: {
- input: 10,
- output: 50,
- cacheRead: 1,
- cacheWrite: 12.5,
- },
- contextWindow: 1000000,
- maxTokens: 128000,
- } satisfies Model<"anthropic-messages">,
"claude-haiku-4-5": {
id: "claude-haiku-4-5",
name: "Claude Haiku 4.5",
@@ -207,7 +188,7 @@ export const OPENCODE_MODELS = {
api: "openai-completions",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
- compat: {"maxTokensField":"max_tokens","requiresReasoningContentOnAssistantMessages":true,"thinkingFormat":"deepseek"},
+ compat: {"maxTokensField":"max_tokens","supportsLongCacheRetention":false,"requiresReasoningContentOnAssistantMessages":true},
reasoning: true,
thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"max"},
input: ["text"],
@@ -226,7 +207,7 @@ export const OPENCODE_MODELS = {
api: "openai-completions",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
- compat: {"maxTokensField":"max_tokens","requiresReasoningContentOnAssistantMessages":true,"thinkingFormat":"deepseek"},
+ compat: {"maxTokensField":"max_tokens","requiresReasoningContentOnAssistantMessages":true},
reasoning: true,
thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"max"},
input: ["text"],
@@ -245,7 +226,7 @@ export const OPENCODE_MODELS = {
api: "openai-completions",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
- compat: {"maxTokensField":"max_tokens","requiresReasoningContentOnAssistantMessages":true,"thinkingFormat":"deepseek"},
+ compat: {"maxTokensField":"max_tokens","supportsLongCacheRetention":false,"requiresReasoningContentOnAssistantMessages":true},
reasoning: true,
thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"max"},
input: ["text"],
@@ -661,7 +642,7 @@ export const OPENCODE_MODELS = {
api: "openai-completions",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
- compat: {"maxTokensField":"max_tokens"},
+ compat: {"maxTokensField":"max_tokens","supportsLongCacheRetention":false},
reasoning: true,
input: ["text", "image"],
cost: {
@@ -679,7 +660,7 @@ export const OPENCODE_MODELS = {
api: "openai-completions",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
- compat: {"thinkingFormat":"deepseek","supportsReasoningEffort":false,"maxTokensField":"max_tokens"},
+ compat: {"thinkingFormat":"deepseek","supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsLongCacheRetention":false},
reasoning: true,
input: ["text", "image"],
cost: {
@@ -733,7 +714,7 @@ export const OPENCODE_MODELS = {
api: "openai-completions",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
- compat: {"maxTokensField":"max_tokens"},
+ compat: {"maxTokensField":"max_tokens","supportsLongCacheRetention":false},
reasoning: true,
input: ["text"],
cost: {
diff --git a/packages/ai/src/providers/openrouter.models.ts b/packages/ai/src/providers/openrouter.models.ts
index 82f78734..02a5d3b9 100644
--- a/packages/ai/src/providers/openrouter.models.ts
+++ b/packages/ai/src/providers/openrouter.models.ts
@@ -98,8 +98,8 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.7999999999999999,
- output: 3.1999999999999997,
+ input: 0.8,
+ output: 3.2,
cacheRead: 0,
cacheWrite: 0,
},
@@ -123,23 +123,6 @@ export const OPENROUTER_MODELS = {
contextWindow: 200000,
maxTokens: 4096,
} satisfies Model<"openai-completions">,
- "anthropic/claude-3.5-haiku": {
- id: "anthropic/claude-3.5-haiku",
- name: "Anthropic: Claude 3.5 Haiku",
- api: "openai-completions",
- provider: "openrouter",
- baseUrl: "https://openrouter.ai/api/v1",
- reasoning: false,
- input: ["text", "image"],
- cost: {
- input: 0.7999999999999999,
- output: 4,
- cacheRead: 0.08,
- cacheWrite: 1,
- },
- contextWindow: 200000,
- maxTokens: 8192,
- } satisfies Model<"openai-completions">,
"anthropic/claude-fable-5": {
id: "anthropic/claude-fable-5",
name: "Anthropic: Claude Fable 5",
@@ -168,7 +151,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 1,
output: 5,
- cacheRead: 0.09999999999999999,
+ cacheRead: 0.1,
cacheWrite: 1.25,
},
contextWindow: 200000,
@@ -393,13 +376,13 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text"],
cost: {
- input: 0.22,
- output: 0.85,
+ input: 0.25,
+ output: 0.8,
cacheRead: 0.06,
cacheWrite: 0,
},
contextWindow: 262144,
- maxTokens: 262144,
+ maxTokens: 80000,
} satisfies Model<"openai-completions">,
"arcee-ai/trinity-mini": {
id: "arcee-ai/trinity-mini",
@@ -512,8 +495,8 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.09999999999999999,
- output: 0.39999999999999997,
+ input: 0.1,
+ output: 0.4,
cacheRead: 0,
cacheWrite: 0,
},
@@ -554,6 +537,23 @@ export const OPENROUTER_MODELS = {
contextWindow: 128000,
maxTokens: 4000,
} satisfies Model<"openai-completions">,
+ "cohere/north-mini-code:free": {
+ id: "cohere/north-mini-code:free",
+ name: "Cohere: North Mini Code (free)",
+ api: "openai-completions",
+ provider: "openrouter",
+ baseUrl: "https://openrouter.ai/api/v1",
+ reasoning: true,
+ input: ["text"],
+ cost: {
+ input: 0,
+ output: 0,
+ cacheRead: 0,
+ cacheWrite: 0,
+ },
+ contextWindow: 256000,
+ maxTokens: 64000,
+ } satisfies Model<"openai-completions">,
"deepseek/deepseek-chat": {
id: "deepseek/deepseek-chat",
name: "DeepSeek: DeepSeek V3",
@@ -563,8 +563,8 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text"],
cost: {
- input: 0.20020000000000002,
- output: 0.8000999999999999,
+ input: 0.2002,
+ output: 0.8001,
cacheRead: 0,
cacheWrite: 0,
},
@@ -580,12 +580,12 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text"],
cost: {
- input: 0.19999999999999998,
+ input: 0.2,
output: 0.77,
cacheRead: 0.135,
cacheWrite: 0,
},
- contextWindow: 131072,
+ contextWindow: 163840,
maxTokens: 16384,
} satisfies Model<"openai-completions">,
"deepseek/deepseek-chat-v3.1": {
@@ -598,7 +598,7 @@ export const OPENROUTER_MODELS = {
input: ["text"],
cost: {
input: 0.21,
- output: 0.7899999999999999,
+ output: 0.79,
cacheRead: 0.13,
cacheWrite: 0,
},
@@ -632,7 +632,7 @@ export const OPENROUTER_MODELS = {
input: ["text"],
cost: {
input: 0.5,
- output: 2.1500000000000004,
+ output: 2.15,
cacheRead: 0.35,
cacheWrite: 0,
},
@@ -701,13 +701,13 @@ export const OPENROUTER_MODELS = {
thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":"xhigh"},
input: ["text"],
cost: {
- input: 0.0983,
- output: 0.1966,
- cacheRead: 0.019700000000000002,
+ input: 0.09,
+ output: 0.18,
+ cacheRead: 0.02,
cacheWrite: 0,
},
contextWindow: 1048576,
- maxTokens: 131072,
+ maxTokens: 65536,
} satisfies Model<"openai-completions">,
"deepseek/deepseek-v4-pro": {
id: "deepseek/deepseek-v4-pro",
@@ -757,7 +757,7 @@ export const OPENROUTER_MODELS = {
input: 0.3,
output: 2.5,
cacheRead: 0.03,
- cacheWrite: 0.08333333333333334,
+ cacheWrite: 0.083333,
},
contextWindow: 1048576,
maxTokens: 65535,
@@ -771,10 +771,10 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.09999999999999999,
- output: 0.39999999999999997,
+ input: 0.1,
+ output: 0.4,
cacheRead: 0.01,
- cacheWrite: 0.08333333333333334,
+ cacheWrite: 0.083333,
},
contextWindow: 1048576,
maxTokens: 65535,
@@ -788,10 +788,10 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.09999999999999999,
- output: 0.39999999999999997,
+ input: 0.1,
+ output: 0.4,
cacheRead: 0.01,
- cacheWrite: 0.08333333333333334,
+ cacheWrite: 0.083333,
},
contextWindow: 1048576,
maxTokens: 65535,
@@ -858,11 +858,28 @@ export const OPENROUTER_MODELS = {
cost: {
input: 0.5,
output: 3,
- cacheRead: 0.049999999999999996,
- cacheWrite: 0.08333333333333334,
+ cacheRead: 0.05,
+ cacheWrite: 0.083333,
},
contextWindow: 1048576,
- maxTokens: 65536,
+ maxTokens: 65535,
+ } satisfies Model<"openai-completions">,
+ "google/gemini-3-pro-image": {
+ id: "google/gemini-3-pro-image",
+ name: "Google: Nano Banana Pro (Gemini 3 Pro Image)",
+ api: "openai-completions",
+ provider: "openrouter",
+ baseUrl: "https://openrouter.ai/api/v1",
+ reasoning: true,
+ input: ["text", "image"],
+ cost: {
+ input: 2,
+ output: 12,
+ cacheRead: 0.2,
+ cacheWrite: 0.375,
+ },
+ contextWindow: 65536,
+ maxTokens: 32768,
} satisfies Model<"openai-completions">,
"google/gemini-3.1-flash-lite": {
id: "google/gemini-3.1-flash-lite",
@@ -875,8 +892,8 @@ export const OPENROUTER_MODELS = {
cost: {
input: 0.25,
output: 1.5,
- cacheRead: 0.024999999999999998,
- cacheWrite: 0.08333333333333334,
+ cacheRead: 0.025,
+ cacheWrite: 0.083333,
},
contextWindow: 1048576,
maxTokens: 65536,
@@ -892,8 +909,8 @@ export const OPENROUTER_MODELS = {
cost: {
input: 0.25,
output: 1.5,
- cacheRead: 0.024999999999999998,
- cacheWrite: 0.08333333333333334,
+ cacheRead: 0.025,
+ cacheWrite: 0.083333,
},
contextWindow: 1048576,
maxTokens: 65536,
@@ -909,7 +926,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 2,
output: 12,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0.375,
},
contextWindow: 1048576,
@@ -926,7 +943,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 2,
output: 12,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0.375,
},
contextWindow: 1048756,
@@ -944,7 +961,7 @@ export const OPENROUTER_MODELS = {
input: 1.5,
output: 9,
cacheRead: 0.15,
- cacheWrite: 0.08333333333333334,
+ cacheWrite: 0.083333,
},
contextWindow: 1048576,
maxTokens: 65536,
@@ -958,7 +975,7 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.049999999999999996,
+ input: 0.05,
output: 0.15,
cacheRead: 0,
cacheWrite: 0,
@@ -1027,12 +1044,12 @@ export const OPENROUTER_MODELS = {
input: ["text", "image"],
cost: {
input: 0.12,
- output: 0.36,
+ output: 0.35,
cacheRead: 0.09,
cacheWrite: 0,
},
contextWindow: 262144,
- maxTokens: 8192,
+ maxTokens: 262144,
} satisfies Model<"openai-completions">,
"google/gemma-4-31b-it:free": {
id: "google/gemma-4-31b-it:free",
@@ -1049,7 +1066,7 @@ export const OPENROUTER_MODELS = {
cacheWrite: 0,
},
contextWindow: 262144,
- maxTokens: 32768,
+ maxTokens: 8192,
} satisfies Model<"openai-completions">,
"ibm-granite/granite-4.1-8b": {
id: "ibm-granite/granite-4.1-8b",
@@ -1060,9 +1077,9 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text"],
cost: {
- input: 0.049999999999999996,
- output: 0.09999999999999999,
- cacheRead: 0.049999999999999996,
+ input: 0.05,
+ output: 0.1,
+ cacheRead: 0.05,
cacheWrite: 0,
},
contextWindow: 131072,
@@ -1080,7 +1097,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 0.25,
output: 0.75,
- cacheRead: 0.024999999999999998,
+ cacheRead: 0.025,
cacheWrite: 0,
},
contextWindow: 128000,
@@ -1154,6 +1171,23 @@ export const OPENROUTER_MODELS = {
contextWindow: 256000,
maxTokens: 80000,
} satisfies Model<"openai-completions">,
+ "liquid/lfm-2.5-1.2b-thinking:free": {
+ id: "liquid/lfm-2.5-1.2b-thinking:free",
+ name: "LiquidAI: LFM2.5-1.2B-Thinking (free)",
+ api: "openai-completions",
+ provider: "openrouter",
+ baseUrl: "https://openrouter.ai/api/v1",
+ reasoning: true,
+ input: ["text"],
+ cost: {
+ input: 0,
+ output: 0,
+ cacheRead: 0,
+ cacheWrite: 0,
+ },
+ contextWindow: 32768,
+ maxTokens: 4096,
+ } satisfies Model<"openai-completions">,
"meta-llama/llama-3.1-70b-instruct": {
id: "meta-llama/llama-3.1-70b-instruct",
name: "Meta: Llama 3.1 70B Instruct",
@@ -1163,8 +1197,8 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text"],
cost: {
- input: 0.39999999999999997,
- output: 0.39999999999999997,
+ input: 0.4,
+ output: 0.4,
cacheRead: 0,
cacheWrite: 0,
},
@@ -1197,7 +1231,7 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text"],
cost: {
- input: 0.09999999999999999,
+ input: 0.1,
output: 0.32,
cacheRead: 0,
cacheWrite: 0,
@@ -1248,7 +1282,7 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.09999999999999999,
+ input: 0.1,
output: 0.3,
cacheRead: 0,
cacheWrite: 0,
@@ -1265,7 +1299,7 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text"],
cost: {
- input: 0.39999999999999997,
+ input: 0.4,
output: 2.2,
cacheRead: 0,
cacheWrite: 0,
@@ -1317,8 +1351,8 @@ export const OPENROUTER_MODELS = {
input: ["text"],
cost: {
input: 0.15,
- output: 0.8999999999999999,
- cacheRead: 0.049999999999999996,
+ output: 0.9,
+ cacheRead: 0.05,
cacheWrite: 0,
},
contextWindow: 204800,
@@ -1333,9 +1367,9 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text"],
cost: {
- input: 0.27,
- output: 1.08,
- cacheRead: 0.054,
+ input: 0.25,
+ output: 1,
+ cacheRead: 0.05,
cacheWrite: 0,
},
contextWindow: 204800,
@@ -1368,7 +1402,7 @@ export const OPENROUTER_MODELS = {
input: ["text"],
cost: {
input: 0.3,
- output: 0.8999999999999999,
+ output: 0.9,
cacheRead: 0.03,
cacheWrite: 0,
},
@@ -1384,7 +1418,7 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text"],
cost: {
- input: 0.39999999999999997,
+ input: 0.4,
output: 2,
cacheRead: 0.04,
cacheWrite: 0,
@@ -1401,8 +1435,8 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.19999999999999998,
- output: 0.19999999999999998,
+ input: 0.2,
+ output: 0.2,
cacheRead: 0.02,
cacheWrite: 0,
},
@@ -1418,8 +1452,8 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.09999999999999999,
- output: 0.09999999999999999,
+ input: 0.1,
+ output: 0.1,
cacheRead: 0.01,
cacheWrite: 0,
},
@@ -1454,7 +1488,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 2,
output: 6,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 128000,
@@ -1471,7 +1505,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 2,
output: 6,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 131072,
@@ -1488,7 +1522,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 0.5,
output: 1.5,
- cacheRead: 0.049999999999999996,
+ cacheRead: 0.05,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -1503,7 +1537,7 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.39999999999999997,
+ input: 0.4,
output: 2,
cacheRead: 0.04,
cacheWrite: 0,
@@ -1537,7 +1571,7 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.39999999999999997,
+ input: 0.4,
output: 2,
cacheRead: 0.04,
cacheWrite: 0,
@@ -1571,7 +1605,7 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text"],
cost: {
- input: 0.19999999999999998,
+ input: 0.2,
output: 0.6,
cacheRead: 0.02,
cacheWrite: 0,
@@ -1606,7 +1640,7 @@ export const OPENROUTER_MODELS = {
input: ["text", "image"],
cost: {
input: 0.075,
- output: 0.19999999999999998,
+ output: 0.2,
cacheRead: 0,
cacheWrite: 0,
},
@@ -1624,7 +1658,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 2,
output: 6,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 65536,
@@ -1639,7 +1673,7 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text"],
cost: {
- input: 0.09999999999999999,
+ input: 0.1,
output: 0.3,
cacheRead: 0.01,
cacheWrite: 0,
@@ -1656,7 +1690,7 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text"],
cost: {
- input: 0.5700000000000001,
+ input: 0.57,
output: 2.3,
cacheRead: 0,
cacheWrite: 0,
@@ -1725,13 +1759,30 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.6799999999999999,
+ input: 0.66,
output: 3.41,
- cacheRead: 0.33999999999999997,
+ cacheRead: 0.144,
cacheWrite: 0,
},
contextWindow: 262144,
- maxTokens: 262142,
+ maxTokens: 262144,
+ } satisfies Model<"openai-completions">,
+ "moonshotai/kimi-k2.7-code": {
+ id: "moonshotai/kimi-k2.7-code",
+ name: "MoonshotAI: Kimi K2.7 Code",
+ api: "openai-completions",
+ provider: "openrouter",
+ baseUrl: "https://openrouter.ai/api/v1",
+ reasoning: true,
+ input: ["text", "image"],
+ cost: {
+ input: 0.612,
+ output: 3.069,
+ cacheRead: 0.1296,
+ cacheWrite: 0,
+ },
+ contextWindow: 262144,
+ maxTokens: 262144,
} satisfies Model<"openai-completions">,
"nex-agi/nex-n2-pro:free": {
id: "nex-agi/nex-n2-pro:free",
@@ -1748,7 +1799,7 @@ export const OPENROUTER_MODELS = {
cacheWrite: 0,
},
contextWindow: 262144,
- maxTokens: 262144,
+ maxTokens: 256000,
} satisfies Model<"openai-completions">,
"nvidia/llama-3.3-nemotron-super-49b-v1.5": {
id: "nvidia/llama-3.3-nemotron-super-49b-v1.5",
@@ -1759,8 +1810,8 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text"],
cost: {
- input: 0.39999999999999997,
- output: 0.39999999999999997,
+ input: 0.4,
+ output: 0.4,
cacheRead: 0,
cacheWrite: 0,
},
@@ -1776,8 +1827,8 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text"],
cost: {
- input: 0.049999999999999996,
- output: 0.19999999999999998,
+ input: 0.05,
+ output: 0.2,
cacheRead: 0,
cacheWrite: 0,
},
@@ -1828,7 +1879,7 @@ export const OPENROUTER_MODELS = {
input: ["text"],
cost: {
input: 0.09,
- output: 0.44999999999999996,
+ output: 0.45,
cacheRead: 0,
cacheWrite: 0,
},
@@ -1862,8 +1913,8 @@ export const OPENROUTER_MODELS = {
input: ["text"],
cost: {
input: 0.5,
- output: 2.5,
- cacheRead: 0.15,
+ output: 2.2,
+ cacheRead: 0.1,
cacheWrite: 0,
},
contextWindow: 1000000,
@@ -1903,23 +1954,6 @@ export const OPENROUTER_MODELS = {
contextWindow: 128000,
maxTokens: 128000,
} satisfies Model<"openai-completions">,
- "nvidia/nemotron-nano-9b-v2": {
- id: "nvidia/nemotron-nano-9b-v2",
- name: "NVIDIA: Nemotron Nano 9B V2",
- api: "openai-completions",
- provider: "openrouter",
- baseUrl: "https://openrouter.ai/api/v1",
- reasoning: true,
- input: ["text"],
- cost: {
- input: 0.04,
- output: 0.16,
- cacheRead: 0,
- cacheWrite: 0,
- },
- contextWindow: 131072,
- maxTokens: 16384,
- } satisfies Model<"openai-completions">,
"nvidia/nemotron-nano-9b-v2:free": {
id: "nvidia/nemotron-nano-9b-v2:free",
name: "NVIDIA: Nemotron Nano 9B V2 (free)",
@@ -2065,9 +2099,9 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.39999999999999997,
- output: 1.5999999999999999,
- cacheRead: 0.09999999999999999,
+ input: 0.4,
+ output: 1.6,
+ cacheRead: 0.1,
cacheWrite: 0,
},
contextWindow: 1047576,
@@ -2082,9 +2116,9 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.09999999999999999,
- output: 0.39999999999999997,
- cacheRead: 0.024999999999999998,
+ input: 0.1,
+ output: 0.4,
+ cacheRead: 0.025,
cacheWrite: 0,
},
contextWindow: 1047576,
@@ -2237,7 +2271,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 0.25,
output: 2,
- cacheRead: 0.024999999999999998,
+ cacheRead: 0.025,
cacheWrite: 0,
},
contextWindow: 400000,
@@ -2252,8 +2286,8 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.049999999999999996,
- output: 0.39999999999999997,
+ input: 0.05,
+ output: 0.4,
cacheRead: 0.01,
cacheWrite: 0,
},
@@ -2356,7 +2390,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 0.25,
output: 2,
- cacheRead: 0.024999999999999998,
+ cacheRead: 0.025,
cacheWrite: 0,
},
contextWindow: 400000,
@@ -2516,7 +2550,7 @@ export const OPENROUTER_MODELS = {
thinkingLevelMap: {"xhigh":"xhigh"},
input: ["text", "image"],
cost: {
- input: 0.19999999999999998,
+ input: 0.2,
output: 1.25,
cacheRead: 0.02,
cacheWrite: 0,
@@ -2695,7 +2729,7 @@ export const OPENROUTER_MODELS = {
cacheWrite: 0,
},
contextWindow: 131072,
- maxTokens: 8192,
+ maxTokens: 32768,
} satisfies Model<"openai-completions">,
"openai/gpt-oss-safeguard-20b": {
id: "openai/gpt-oss-safeguard-20b",
@@ -2708,7 +2742,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 0.075,
output: 0.3,
- cacheRead: 0.037,
+ cacheRead: 0.0375,
cacheWrite: 0,
},
contextWindow: 131072,
@@ -2901,6 +2935,23 @@ export const OPENROUTER_MODELS = {
contextWindow: 200000,
maxTokens: 4096,
} satisfies Model<"openai-completions">,
+ "openrouter/fusion": {
+ id: "openrouter/fusion",
+ name: "OpenRouter: Fusion",
+ api: "openai-completions",
+ provider: "openrouter",
+ baseUrl: "https://openrouter.ai/api/v1",
+ reasoning: true,
+ input: ["text"],
+ cost: {
+ input: 0,
+ output: 0,
+ cacheRead: 0,
+ cacheWrite: 0,
+ },
+ contextWindow: 1000000,
+ maxTokens: 30000,
+ } satisfies Model<"openai-completions">,
"openrouter/owl-alpha": {
id: "openrouter/owl-alpha",
name: "Owl Alpha",
@@ -2918,6 +2969,23 @@ export const OPENROUTER_MODELS = {
contextWindow: 1048756,
maxTokens: 262144,
} satisfies Model<"openai-completions">,
+ "poolside/laguna-m.1": {
+ id: "poolside/laguna-m.1",
+ name: "Poolside: Laguna M.1",
+ api: "openai-completions",
+ provider: "openrouter",
+ baseUrl: "https://openrouter.ai/api/v1",
+ reasoning: true,
+ input: ["text"],
+ cost: {
+ input: 0.2,
+ output: 0.4,
+ cacheRead: 0.1,
+ cacheWrite: 0,
+ },
+ contextWindow: 262144,
+ maxTokens: 32768,
+ } satisfies Model<"openai-completions">,
"poolside/laguna-m.1:free": {
id: "poolside/laguna-m.1:free",
name: "Poolside: Laguna M.1 (free)",
@@ -2935,6 +3003,23 @@ export const OPENROUTER_MODELS = {
contextWindow: 262144,
maxTokens: 32768,
} satisfies Model<"openai-completions">,
+ "poolside/laguna-xs.2": {
+ id: "poolside/laguna-xs.2",
+ name: "Poolside: Laguna XS.2",
+ api: "openai-completions",
+ provider: "openrouter",
+ baseUrl: "https://openrouter.ai/api/v1",
+ reasoning: true,
+ input: ["text"],
+ cost: {
+ input: 0.1,
+ output: 0.2,
+ cacheRead: 0.05,
+ cacheWrite: 0,
+ },
+ contextWindow: 262144,
+ maxTokens: 32768,
+ } satisfies Model<"openai-completions">,
"poolside/laguna-xs.2:free": {
id: "poolside/laguna-xs.2:free",
name: "Poolside: Laguna XS.2 (free)",
@@ -2961,7 +3046,7 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text"],
cost: {
- input: 0.19999999999999998,
+ input: 0.2,
output: 1.1,
cacheRead: 0,
cacheWrite: 0,
@@ -2979,13 +3064,30 @@ export const OPENROUTER_MODELS = {
input: ["text"],
cost: {
input: 0.36,
- output: 0.39999999999999997,
+ output: 0.4,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 131072,
maxTokens: 16384,
} satisfies Model<"openai-completions">,
+ "qwen/qwen-2.5-7b-instruct": {
+ id: "qwen/qwen-2.5-7b-instruct",
+ name: "Qwen: Qwen2.5 7B Instruct",
+ api: "openai-completions",
+ provider: "openrouter",
+ baseUrl: "https://openrouter.ai/api/v1",
+ reasoning: false,
+ input: ["text"],
+ cost: {
+ input: 0.04,
+ output: 0.1,
+ cacheRead: 0,
+ cacheWrite: 0,
+ },
+ contextWindow: 131072,
+ maxTokens: 32768,
+ } satisfies Model<"openai-completions">,
"qwen/qwen-plus": {
id: "qwen/qwen-plus",
name: "Qwen: Qwen-Plus",
@@ -2997,7 +3099,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 0.26,
output: 0.78,
- cacheRead: 0.052000000000000005,
+ cacheRead: 0.052,
cacheWrite: 0.325,
},
contextWindow: 1000000,
@@ -3046,7 +3148,7 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text"],
cost: {
- input: 0.09999999999999999,
+ input: 0.1,
output: 0.24,
cacheRead: 0,
cacheWrite: 0,
@@ -3063,8 +3165,8 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text"],
cost: {
- input: 0.45499999999999996,
- output: 1.8199999999999998,
+ input: 0.455,
+ output: 1.82,
cacheRead: 0,
cacheWrite: 0,
},
@@ -3081,7 +3183,7 @@ export const OPENROUTER_MODELS = {
input: ["text"],
cost: {
input: 0.09,
- output: 0.09999999999999999,
+ output: 0.1,
cacheRead: 0,
cacheWrite: 0,
},
@@ -3097,9 +3199,9 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text"],
cost: {
- input: 0.09999999999999999,
- output: 0.09999999999999999,
- cacheRead: 0.09999999999999999,
+ input: 0.1,
+ output: 0.1,
+ cacheRead: 0.1,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -3149,7 +3251,7 @@ export const OPENROUTER_MODELS = {
input: ["text"],
cost: {
input: 0.08,
- output: 0.39999999999999997,
+ output: 0.4,
cacheRead: 0.08,
cacheWrite: 0,
},
@@ -3182,9 +3284,9 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text"],
cost: {
- input: 0.049999999999999996,
- output: 0.39999999999999997,
- cacheRead: 0.049999999999999996,
+ input: 0.05,
+ output: 0.4,
+ cacheRead: 0.05,
cacheWrite: 0,
},
contextWindow: 131072,
@@ -3200,7 +3302,7 @@ export const OPENROUTER_MODELS = {
input: ["text"],
cost: {
input: 0.22,
- output: 1.7999999999999998,
+ output: 1.8,
cacheRead: 0,
cacheWrite: 0,
},
@@ -3251,7 +3353,7 @@ export const OPENROUTER_MODELS = {
input: ["text"],
cost: {
input: 0.11,
- output: 0.7999999999999999,
+ output: 0.8,
cacheRead: 0.07,
cacheWrite: 0,
},
@@ -3386,7 +3488,7 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.19999999999999998,
+ input: 0.2,
output: 0.88,
cacheRead: 0.11,
cacheWrite: 0,
@@ -3454,8 +3556,8 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.10400000000000001,
- output: 0.41600000000000004,
+ input: 0.104,
+ output: 0.416,
cacheRead: 0,
cacheWrite: 0,
},
@@ -3541,7 +3643,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 0.14,
output: 1,
- cacheRead: 0.049999999999999996,
+ cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -3556,13 +3658,13 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.39,
- output: 2.34,
+ input: 0.385,
+ output: 2.45,
cacheRead: 0,
cacheWrite: 0,
},
- contextWindow: 262144,
- maxTokens: 65536,
+ contextWindow: 256000,
+ maxTokens: 4096,
} satisfies Model<"openai-completions">,
"qwen/qwen3.5-9b": {
id: "qwen/qwen3.5-9b",
@@ -3573,7 +3675,7 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.09999999999999999,
+ input: 0.1,
output: 0.15,
cacheRead: 0,
cacheWrite: 0,
@@ -3625,7 +3727,7 @@ export const OPENROUTER_MODELS = {
input: ["text", "image"],
cost: {
input: 0.3,
- output: 1.7999999999999998,
+ output: 1.8,
cacheRead: 0,
cacheWrite: 0.375,
},
@@ -3641,13 +3743,13 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.28900000000000003,
- output: 2.4,
+ input: 0.2885,
+ output: 3.17,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 262144,
- maxTokens: 131072,
+ maxTokens: 262140,
} satisfies Model<"openai-completions">,
"qwen/qwen3.6-35b-a3b": {
id: "qwen/qwen3.6-35b-a3b",
@@ -3664,7 +3766,7 @@ export const OPENROUTER_MODELS = {
cacheWrite: 0,
},
contextWindow: 262144,
- maxTokens: 262140,
+ maxTokens: 262144,
} satisfies Model<"openai-completions">,
"qwen/qwen3.6-flash": {
id: "qwen/qwen3.6-flash",
@@ -3743,10 +3845,10 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.39999999999999997,
- output: 1.5999999999999999,
- cacheRead: 0.08,
- cacheWrite: 0.5,
+ input: 0.32,
+ output: 1.28,
+ cacheRead: 0.064,
+ cacheWrite: 0.4,
},
contextWindow: 1000000,
maxTokens: 65536,
@@ -3760,8 +3862,8 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.09999999999999999,
- output: 0.09999999999999999,
+ input: 0.1,
+ output: 0.1,
cacheRead: 0,
cacheWrite: 0,
},
@@ -3828,7 +3930,7 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.19999999999999998,
+ input: 0.2,
output: 1.15,
cacheRead: 0.04,
cacheWrite: 0,
@@ -3847,7 +3949,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 0.063,
output: 0.21,
- cacheRead: 0.020999999999999998,
+ cacheRead: 0.021,
cacheWrite: 0,
},
contextWindow: 262144,
@@ -3862,7 +3964,7 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text"],
cost: {
- input: 0.16999999999999998,
+ input: 0.17,
output: 0.43,
cacheRead: 0,
cacheWrite: 0,
@@ -3879,8 +3981,8 @@ export const OPENROUTER_MODELS = {
reasoning: false,
input: ["text"],
cost: {
- input: 0.39999999999999997,
- output: 0.39999999999999997,
+ input: 0.4,
+ output: 0.4,
cacheRead: 0,
cacheWrite: 0,
},
@@ -3915,7 +4017,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 1.25,
output: 2.5,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 2000000,
@@ -3932,7 +4034,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 1.25,
output: 2.5,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 1000000,
@@ -3949,29 +4051,12 @@ export const OPENROUTER_MODELS = {
cost: {
input: 1,
output: 2,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 256000,
maxTokens: 4096,
} satisfies Model<"openai-completions">,
- "xiaomi/mimo-v2-flash": {
- id: "xiaomi/mimo-v2-flash",
- name: "Xiaomi: MiMo-V2-Flash",
- api: "openai-completions",
- provider: "openrouter",
- baseUrl: "https://openrouter.ai/api/v1",
- reasoning: true,
- input: ["text"],
- cost: {
- input: 0.09999999999999999,
- output: 0.3,
- cacheRead: 0.01,
- cacheWrite: 0,
- },
- contextWindow: 262144,
- maxTokens: 65536,
- } satisfies Model<"openai-completions">,
"xiaomi/mimo-v2.5": {
id: "xiaomi/mimo-v2.5",
name: "Xiaomi: MiMo-V2.5",
@@ -4032,13 +4117,13 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text"],
cost: {
- input: 0.125,
+ input: 0.13,
output: 0.85,
- cacheRead: 0.06,
+ cacheRead: 0.025,
cacheWrite: 0,
},
contextWindow: 131072,
- maxTokens: 131070,
+ maxTokens: 98304,
} satisfies Model<"openai-completions">,
"z-ai/glm-4.5v": {
id: "z-ai/glm-4.5v",
@@ -4050,7 +4135,7 @@ export const OPENROUTER_MODELS = {
input: ["text", "image"],
cost: {
input: 0.6,
- output: 1.7999999999999998,
+ output: 1.8,
cacheRead: 0.11,
cacheWrite: 0,
},
@@ -4084,7 +4169,7 @@ export const OPENROUTER_MODELS = {
input: ["text", "image"],
cost: {
input: 0.3,
- output: 0.8999999999999999,
+ output: 0.9,
cacheRead: 0.055,
cacheWrite: 0,
},
@@ -4100,7 +4185,7 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text"],
cost: {
- input: 0.39999999999999997,
+ input: 0.4,
output: 1.75,
cacheRead: 0.08,
cacheWrite: 0,
@@ -4118,7 +4203,7 @@ export const OPENROUTER_MODELS = {
input: ["text"],
cost: {
input: 0.06,
- output: 0.39999999999999997,
+ output: 0.4,
cacheRead: 0.01,
cacheWrite: 0,
},
@@ -4170,11 +4255,29 @@ export const OPENROUTER_MODELS = {
cost: {
input: 0.98,
output: 3.08,
- cacheRead: 0.182,
+ cacheRead: 0.49,
cacheWrite: 0,
},
contextWindow: 202752,
- maxTokens: 4096,
+ maxTokens: 65535,
+ } satisfies Model<"openai-completions">,
+ "z-ai/glm-5.2": {
+ id: "z-ai/glm-5.2",
+ name: "Z.ai: GLM 5.2",
+ api: "openai-completions",
+ provider: "openrouter",
+ baseUrl: "https://openrouter.ai/api/v1",
+ reasoning: true,
+ thinkingLevelMap: {"xhigh":"xhigh"},
+ input: ["text"],
+ cost: {
+ input: 1,
+ output: 4,
+ cacheRead: 0.18,
+ cacheWrite: 0,
+ },
+ contextWindow: 1048576,
+ maxTokens: 32768,
} satisfies Model<"openai-completions">,
"~anthropic/claude-fable-latest": {
id: "~anthropic/claude-fable-latest",
@@ -4204,7 +4307,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 1,
output: 5,
- cacheRead: 0.09999999999999999,
+ cacheRead: 0.1,
cacheWrite: 1.25,
},
contextWindow: 200000,
@@ -4256,7 +4359,7 @@ export const OPENROUTER_MODELS = {
input: 1.5,
output: 9,
cacheRead: 0.15,
- cacheWrite: 0.08333333333333334,
+ cacheWrite: 0.083333,
},
contextWindow: 1048576,
maxTokens: 65536,
@@ -4272,7 +4375,7 @@ export const OPENROUTER_MODELS = {
cost: {
input: 2,
output: 12,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0.375,
},
contextWindow: 1048576,
@@ -4287,13 +4390,13 @@ export const OPENROUTER_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.6799999999999999,
+ input: 0.66,
output: 3.41,
- cacheRead: 0.33999999999999997,
+ cacheRead: 0.144,
cacheWrite: 0,
},
contextWindow: 262144,
- maxTokens: 262142,
+ maxTokens: 262144,
} satisfies Model<"openai-completions">,
"~openai/gpt-latest": {
id: "~openai/gpt-latest",
diff --git a/packages/ai/src/providers/together.models.ts b/packages/ai/src/providers/together.models.ts
index 350f87f4..5ba1500c 100644
--- a/packages/ai/src/providers/together.models.ts
+++ b/packages/ai/src/providers/together.models.ts
@@ -4,25 +4,6 @@
import type { Model } from "../types.ts";
export const TOGETHER_MODELS = {
- "MiniMaxAI/MiniMax-M2.5": {
- id: "MiniMaxAI/MiniMax-M2.5",
- name: "MiniMax-M2.5",
- api: "openai-completions",
- provider: "together",
- baseUrl: "https://api.together.ai/v1",
- compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"supportsLongCacheRetention":false,"thinkingFormat":"together"},
- reasoning: true,
- thinkingLevelMap: {"minimal":null,"low":null,"medium":null},
- input: ["text"],
- cost: {
- input: 0.3,
- output: 1.2,
- cacheRead: 0.06,
- cacheWrite: 0,
- },
- contextWindow: 204800,
- maxTokens: 131072,
- } satisfies Model<"openai-completions">,
"MiniMaxAI/MiniMax-M2.7": {
id: "MiniMaxAI/MiniMax-M2.7",
name: "MiniMax-M2.7",
@@ -42,28 +23,28 @@ export const TOGETHER_MODELS = {
contextWindow: 202752,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
- "Qwen/Qwen3-235B-A22B-Instruct-2507-tput": {
- id: "Qwen/Qwen3-235B-A22B-Instruct-2507-tput",
- name: "Qwen3 235B A22B Instruct 2507 FP8",
+ "MiniMaxAI/MiniMax-M3": {
+ id: "MiniMaxAI/MiniMax-M3",
+ name: "MiniMax-M3",
api: "openai-completions",
provider: "together",
baseUrl: "https://api.together.ai/v1",
compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"supportsLongCacheRetention":false,"thinkingFormat":"together"},
reasoning: true,
thinkingLevelMap: {"minimal":null,"low":null,"medium":null},
- input: ["text"],
+ input: ["text", "image"],
cost: {
- input: 0.2,
- output: 0.6,
- cacheRead: 0,
+ input: 0.3,
+ output: 1.2,
+ cacheRead: 0.06,
cacheWrite: 0,
},
- contextWindow: 262144,
- maxTokens: 262144,
+ contextWindow: 524288,
+ maxTokens: 250000,
} satisfies Model<"openai-completions">,
- "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8": {
- id: "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8",
- name: "Qwen3 Coder 480B A35B Instruct",
+ "Qwen/Qwen2.5-7B-Instruct-Turbo": {
+ id: "Qwen/Qwen2.5-7B-Instruct-Turbo",
+ name: "Qwen 2.5 7B Instruct Turbo",
api: "openai-completions",
provider: "together",
baseUrl: "https://api.together.ai/v1",
@@ -71,27 +52,26 @@ export const TOGETHER_MODELS = {
reasoning: false,
input: ["text"],
cost: {
- input: 2,
- output: 2,
+ input: 0.3,
+ output: 0.3,
cacheRead: 0,
cacheWrite: 0,
},
- contextWindow: 262144,
- maxTokens: 262144,
+ contextWindow: 32768,
+ maxTokens: 32768,
} satisfies Model<"openai-completions">,
- "Qwen/Qwen3-Coder-Next-FP8": {
- id: "Qwen/Qwen3-Coder-Next-FP8",
- name: "Qwen3 Coder Next FP8",
+ "Qwen/Qwen3-235B-A22B-Instruct-2507-tput": {
+ id: "Qwen/Qwen3-235B-A22B-Instruct-2507-tput",
+ name: "Qwen3 235B A22B Instruct 2507 FP8",
api: "openai-completions",
provider: "together",
baseUrl: "https://api.together.ai/v1",
- compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"supportsLongCacheRetention":false,"thinkingFormat":"together"},
- reasoning: true,
- thinkingLevelMap: {"minimal":null,"low":null,"medium":null},
+ compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"supportsLongCacheRetention":false},
+ reasoning: false,
input: ["text"],
cost: {
- input: 0.5,
- output: 1.2,
+ input: 0.2,
+ output: 0.6,
cacheRead: 0,
cacheWrite: 0,
},
@@ -117,6 +97,25 @@ export const TOGETHER_MODELS = {
contextWindow: 262144,
maxTokens: 130000,
} satisfies Model<"openai-completions">,
+ "Qwen/Qwen3.5-9B": {
+ id: "Qwen/Qwen3.5-9B",
+ name: "Qwen3.5 9B",
+ api: "openai-completions",
+ provider: "together",
+ baseUrl: "https://api.together.ai/v1",
+ compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"supportsLongCacheRetention":false,"thinkingFormat":"together"},
+ reasoning: true,
+ thinkingLevelMap: {"minimal":null,"low":null,"medium":null},
+ input: ["text", "image"],
+ cost: {
+ input: 0.17,
+ output: 0.25,
+ cacheRead: 0,
+ cacheWrite: 0,
+ },
+ contextWindow: 262144,
+ maxTokens: 65536,
+ } satisfies Model<"openai-completions">,
"Qwen/Qwen3.6-Plus": {
id: "Qwen/Qwen3.6-Plus",
name: "Qwen3.6 Plus",
@@ -142,57 +141,18 @@ export const TOGETHER_MODELS = {
api: "openai-completions",
provider: "together",
baseUrl: "https://api.together.ai/v1",
- compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"supportsLongCacheRetention":false,"thinkingFormat":"together"},
- reasoning: true,
- thinkingLevelMap: {"minimal":null,"low":null,"medium":null},
+ compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"supportsLongCacheRetention":false},
+ reasoning: false,
input: ["text"],
cost: {
- input: 2.5,
- output: 7.5,
+ input: 1.25,
+ output: 3.75,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 1000000,
maxTokens: 500000,
} satisfies Model<"openai-completions">,
- "deepseek-ai/DeepSeek-V3": {
- id: "deepseek-ai/DeepSeek-V3",
- name: "DeepSeek-V3",
- api: "openai-completions",
- provider: "together",
- baseUrl: "https://api.together.ai/v1",
- compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"supportsLongCacheRetention":false,"thinkingFormat":"together"},
- reasoning: true,
- thinkingLevelMap: {"minimal":null,"low":null,"medium":null},
- input: ["text"],
- cost: {
- input: 1.25,
- output: 1.25,
- cacheRead: 0,
- cacheWrite: 0,
- },
- contextWindow: 131072,
- maxTokens: 131072,
- } satisfies Model<"openai-completions">,
- "deepseek-ai/DeepSeek-V3-1": {
- id: "deepseek-ai/DeepSeek-V3-1",
- name: "DeepSeek V3.1",
- api: "openai-completions",
- provider: "together",
- baseUrl: "https://api.together.ai/v1",
- compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"supportsLongCacheRetention":false,"thinkingFormat":"together"},
- reasoning: true,
- thinkingLevelMap: {"minimal":null,"low":null,"medium":null},
- input: ["text"],
- cost: {
- input: 0.6,
- output: 1.7,
- cacheRead: 0,
- cacheWrite: 0,
- },
- contextWindow: 131072,
- maxTokens: 131072,
- } satisfies Model<"openai-completions">,
"deepseek-ai/DeepSeek-V4-Pro": {
id: "deepseek-ai/DeepSeek-V4-Pro",
name: "DeepSeek V4 Pro",
@@ -204,8 +164,8 @@ export const TOGETHER_MODELS = {
thinkingLevelMap: {"minimal":null,"low":null,"medium":null,"high":"high","xhigh":null},
input: ["text"],
cost: {
- input: 2.1,
- output: 4.4,
+ input: 1.74,
+ output: 3.48,
cacheRead: 0.2,
cacheWrite: 0,
},
@@ -241,8 +201,8 @@ export const TOGETHER_MODELS = {
thinkingLevelMap: {"minimal":null,"low":null,"medium":null},
input: ["text", "image"],
cost: {
- input: 0.2,
- output: 0.5,
+ input: 0.39,
+ output: 0.97,
cacheRead: 0,
cacheWrite: 0,
},
@@ -267,25 +227,6 @@ export const TOGETHER_MODELS = {
contextWindow: 131072,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
- "moonshotai/Kimi-K2.5": {
- id: "moonshotai/Kimi-K2.5",
- name: "Kimi K2.5",
- api: "openai-completions",
- provider: "together",
- baseUrl: "https://api.together.ai/v1",
- compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"supportsLongCacheRetention":false,"thinkingFormat":"together"},
- reasoning: true,
- thinkingLevelMap: {"minimal":null,"low":null,"medium":null},
- input: ["text", "image"],
- cost: {
- input: 0.5,
- output: 2.8,
- cacheRead: 0,
- cacheWrite: 0,
- },
- contextWindow: 262144,
- maxTokens: 262144,
- } satisfies Model<"openai-completions">,
"moonshotai/Kimi-K2.6": {
id: "moonshotai/Kimi-K2.6",
name: "Kimi K2.6",
@@ -305,6 +246,25 @@ export const TOGETHER_MODELS = {
contextWindow: 262144,
maxTokens: 131000,
} satisfies Model<"openai-completions">,
+ "moonshotai/Kimi-K2.7-Code": {
+ id: "moonshotai/Kimi-K2.7-Code",
+ name: "Kimi K2.7 Code",
+ api: "openai-completions",
+ provider: "together",
+ baseUrl: "https://api.together.ai/v1",
+ compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"supportsLongCacheRetention":false,"thinkingFormat":"together"},
+ reasoning: true,
+ thinkingLevelMap: {"minimal":null,"low":null,"medium":null},
+ input: ["text"],
+ cost: {
+ input: 0.95,
+ output: 4,
+ cacheRead: 0.19,
+ cacheWrite: 0,
+ },
+ contextWindow: 262144,
+ maxTokens: 131072,
+ } satisfies Model<"openai-completions">,
"nvidia/nemotron-3-ultra-550b-a55b": {
id: "nvidia/nemotron-3-ultra-550b-a55b",
name: "Nemotron 3 Ultra 550B A55B",
@@ -343,6 +303,44 @@ export const TOGETHER_MODELS = {
contextWindow: 131072,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
+ "openai/gpt-oss-20b": {
+ id: "openai/gpt-oss-20b",
+ name: "GPT OSS 20B",
+ api: "openai-completions",
+ provider: "together",
+ baseUrl: "https://api.together.ai/v1",
+ compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":true,"maxTokensField":"max_tokens","supportsStrictMode":false,"supportsLongCacheRetention":false,"thinkingFormat":"openai"},
+ reasoning: true,
+ thinkingLevelMap: {"off":null,"minimal":null},
+ input: ["text"],
+ cost: {
+ input: 0.05,
+ output: 0.2,
+ cacheRead: 0,
+ cacheWrite: 0,
+ },
+ contextWindow: 131072,
+ maxTokens: 131072,
+ } satisfies Model<"openai-completions">,
+ "zai-org/GLM-5": {
+ id: "zai-org/GLM-5",
+ name: "GLM-5",
+ api: "openai-completions",
+ provider: "together",
+ baseUrl: "https://api.together.ai/v1",
+ compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"supportsLongCacheRetention":false,"thinkingFormat":"together"},
+ reasoning: true,
+ thinkingLevelMap: {"minimal":null,"low":null,"medium":null},
+ input: ["text"],
+ cost: {
+ input: 1,
+ output: 3.2,
+ cacheRead: 0,
+ cacheWrite: 0,
+ },
+ contextWindow: 202752,
+ maxTokens: 131072,
+ } satisfies Model<"openai-completions">,
"zai-org/GLM-5.1": {
id: "zai-org/GLM-5.1",
name: "GLM-5.1",
diff --git a/packages/ai/src/providers/vercel-ai-gateway.models.ts b/packages/ai/src/providers/vercel-ai-gateway.models.ts
index 1eb32f12..ea65e49c 100644
--- a/packages/ai/src/providers/vercel-ai-gateway.models.ts
+++ b/packages/ai/src/providers/vercel-ai-gateway.models.ts
@@ -98,7 +98,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.39999999999999997,
+ input: 0.4,
output: 4,
cacheRead: 0,
cacheWrite: 0,
@@ -168,7 +168,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 1,
output: 5,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 1000000,
@@ -268,7 +268,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.39999999999999997,
+ input: 0.4,
output: 4,
cacheRead: 0,
cacheWrite: 0,
@@ -285,8 +285,8 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.09999999999999999,
- output: 0.39999999999999997,
+ input: 0.1,
+ output: 0.4,
cacheRead: 0.001,
cacheWrite: 0.125,
},
@@ -302,7 +302,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.39999999999999997,
+ input: 0.4,
output: 2.4,
cacheRead: 0.04,
cacheWrite: 0.5,
@@ -320,7 +320,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
input: ["text", "image"],
cost: {
input: 0.6,
- output: 3.5999999999999996,
+ output: 3.6,
cacheRead: 0,
cacheWrite: 0,
},
@@ -338,7 +338,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 0.5,
output: 3,
- cacheRead: 0.09999999999999999,
+ cacheRead: 0.1,
cacheWrite: 0.625,
},
contextWindow: 1000000,
@@ -370,8 +370,8 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.39999999999999997,
- output: 1.5999999999999999,
+ input: 0.4,
+ output: 1.6,
cacheRead: 0.08,
cacheWrite: 0.5,
},
@@ -404,7 +404,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.7999999999999999,
+ input: 0.8,
output: 4,
cacheRead: 0.08,
cacheWrite: 1,
@@ -412,25 +412,6 @@ export const VERCEL_AI_GATEWAY_MODELS = {
contextWindow: 200000,
maxTokens: 8192,
} satisfies Model<"anthropic-messages">,
- "anthropic/claude-fable-5": {
- id: "anthropic/claude-fable-5",
- name: "Claude Fable 5",
- api: "anthropic-messages",
- provider: "vercel-ai-gateway",
- baseUrl: "https://ai-gateway.vercel.sh",
- compat: {"forceAdaptiveThinking":true},
- reasoning: true,
- thinkingLevelMap: {"off":null,"xhigh":"xhigh"},
- input: ["text", "image"],
- cost: {
- input: 10,
- output: 50,
- cacheRead: 1,
- cacheWrite: 12.5,
- },
- contextWindow: 1000000,
- maxTokens: 128000,
- } satisfies Model<"anthropic-messages">,
"anthropic/claude-haiku-4.5": {
id: "anthropic/claude-haiku-4.5",
name: "Claude Haiku 4.5",
@@ -442,7 +423,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 1,
output: 5,
- cacheRead: 0.09999999999999999,
+ cacheRead: 0.1,
cacheWrite: 1.25,
},
contextWindow: 200000,
@@ -635,7 +616,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
input: ["text"],
cost: {
input: 0.25,
- output: 0.8999999999999999,
+ output: 0.9,
cacheRead: 0,
cacheWrite: 0,
},
@@ -653,7 +634,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 0.25,
output: 2,
- cacheRead: 0.049999999999999996,
+ cacheRead: 0.05,
cacheWrite: 0,
},
contextWindow: 256000,
@@ -838,8 +819,8 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.09999999999999999,
- output: 0.39999999999999997,
+ input: 0.1,
+ output: 0.4,
cacheRead: 0.01,
cacheWrite: 0,
},
@@ -874,7 +855,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 0.5,
output: 3,
- cacheRead: 0.049999999999999996,
+ cacheRead: 0.05,
cacheWrite: 0,
},
contextWindow: 1000000,
@@ -891,7 +872,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 2,
output: 12,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 1000000,
@@ -942,7 +923,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 2,
output: 12,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 1000000,
@@ -992,7 +973,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
input: ["text", "image"],
cost: {
input: 0.14,
- output: 0.39999999999999997,
+ output: 0.4,
cacheRead: 0,
cacheWrite: 0,
},
@@ -1010,7 +991,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 0.25,
output: 0.75,
- cacheRead: 0.024999999999999998,
+ cacheRead: 0.025,
cacheWrite: 0,
},
contextWindow: 128000,
@@ -1162,7 +1143,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
input: ["text", "image"],
cost: {
input: 0.24,
- output: 0.9700000000000001,
+ output: 0.97,
cacheRead: 0,
cacheWrite: 0,
},
@@ -1178,7 +1159,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.16999999999999998,
+ input: 0.17,
output: 0.66,
cacheRead: 0,
cacheWrite: 0,
@@ -1332,7 +1313,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
input: ["text"],
cost: {
input: 0.3,
- output: 0.8999999999999999,
+ output: 0.9,
cacheRead: 0,
cacheWrite: 0,
},
@@ -1348,7 +1329,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: false,
input: ["text"],
cost: {
- input: 0.39999999999999997,
+ input: 0.4,
output: 2,
cacheRead: 0,
cacheWrite: 0,
@@ -1365,7 +1346,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: false,
input: ["text"],
cost: {
- input: 0.09999999999999999,
+ input: 0.1,
output: 0.3,
cacheRead: 0,
cacheWrite: 0,
@@ -1382,7 +1363,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: false,
input: ["text"],
cost: {
- input: 0.09999999999999999,
+ input: 0.1,
output: 0.3,
cacheRead: 0,
cacheWrite: 0,
@@ -1399,8 +1380,8 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: false,
input: ["text"],
cost: {
- input: 0.09999999999999999,
- output: 0.09999999999999999,
+ input: 0.1,
+ output: 0.1,
cacheRead: 0,
cacheWrite: 0,
},
@@ -1433,7 +1414,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.39999999999999997,
+ input: 0.4,
output: 2,
cacheRead: 0,
cacheWrite: 0,
@@ -1467,13 +1448,13 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: false,
input: ["text"],
cost: {
- input: 0.02,
- output: 0.04,
+ input: 0.15,
+ output: 0.15,
cacheRead: 0,
cacheWrite: 0,
},
- contextWindow: 131072,
- maxTokens: 131072,
+ contextWindow: 128000,
+ maxTokens: 128000,
} satisfies Model<"anthropic-messages">,
"mistral/mistral-small": {
id: "mistral/mistral-small",
@@ -1484,7 +1465,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.09999999999999999,
+ input: 0.1,
output: 0.3,
cacheRead: 0,
cacheWrite: 0,
@@ -1535,7 +1516,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: false,
input: ["text"],
cost: {
- input: 0.5700000000000001,
+ input: 0.57,
output: 2.3,
cacheRead: 0,
cacheWrite: 0,
@@ -1560,40 +1541,6 @@ export const VERCEL_AI_GATEWAY_MODELS = {
contextWindow: 262114,
maxTokens: 262114,
} satisfies Model<"anthropic-messages">,
- "moonshotai/kimi-k2-thinking-turbo": {
- id: "moonshotai/kimi-k2-thinking-turbo",
- name: "Kimi K2 Thinking Turbo",
- api: "anthropic-messages",
- provider: "vercel-ai-gateway",
- baseUrl: "https://ai-gateway.vercel.sh",
- reasoning: true,
- input: ["text"],
- cost: {
- input: 1.15,
- output: 8,
- cacheRead: 0.15,
- cacheWrite: 0,
- },
- contextWindow: 262114,
- maxTokens: 262114,
- } satisfies Model<"anthropic-messages">,
- "moonshotai/kimi-k2-turbo": {
- id: "moonshotai/kimi-k2-turbo",
- name: "Kimi K2 Turbo",
- api: "anthropic-messages",
- provider: "vercel-ai-gateway",
- baseUrl: "https://ai-gateway.vercel.sh",
- reasoning: false,
- input: ["text"],
- cost: {
- input: 1.15,
- output: 8,
- cacheRead: 0.15,
- cacheWrite: 0,
- },
- contextWindow: 256000,
- maxTokens: 16384,
- } satisfies Model<"anthropic-messages">,
"moonshotai/kimi-k2.5": {
id: "moonshotai/kimi-k2.5",
name: "Kimi K2.5",
@@ -1605,7 +1552,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 0.6,
output: 3,
- cacheRead: 0.09999999999999999,
+ cacheRead: 0.1,
cacheWrite: 0,
},
contextWindow: 262114,
@@ -1628,6 +1575,40 @@ export const VERCEL_AI_GATEWAY_MODELS = {
contextWindow: 262000,
maxTokens: 262000,
} satisfies Model<"anthropic-messages">,
+ "moonshotai/kimi-k2.7-code": {
+ id: "moonshotai/kimi-k2.7-code",
+ name: "Kimi K2.7 Code",
+ api: "anthropic-messages",
+ provider: "vercel-ai-gateway",
+ baseUrl: "https://ai-gateway.vercel.sh",
+ reasoning: true,
+ input: ["text", "image"],
+ cost: {
+ input: 0.95,
+ output: 4,
+ cacheRead: 0.19,
+ cacheWrite: 0,
+ },
+ contextWindow: 256000,
+ maxTokens: 32768,
+ } satisfies Model<"anthropic-messages">,
+ "moonshotai/kimi-k2.7-code-highspeed": {
+ id: "moonshotai/kimi-k2.7-code-highspeed",
+ name: "Kimi K2.7 Code High Speed",
+ api: "anthropic-messages",
+ provider: "vercel-ai-gateway",
+ baseUrl: "https://ai-gateway.vercel.sh",
+ reasoning: true,
+ input: ["text", "image"],
+ cost: {
+ input: 1.9,
+ output: 8,
+ cacheRead: 0.38,
+ cacheWrite: 0,
+ },
+ contextWindow: 262144,
+ maxTokens: 32768,
+ } satisfies Model<"anthropic-messages">,
"nvidia/nemotron-3-super-120b-a12b": {
id: "nvidia/nemotron-3-super-120b-a12b",
name: "NVIDIA Nemotron 3 Super 120B A12B",
@@ -1671,7 +1652,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.19999999999999998,
+ input: 0.2,
output: 0.6,
cacheRead: 0,
cacheWrite: 0,
@@ -1689,7 +1670,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
input: ["text"],
cost: {
input: 0.06,
- output: 0.22999999999999998,
+ output: 0.23,
cacheRead: 0,
cacheWrite: 0,
},
@@ -1739,9 +1720,9 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.39999999999999997,
- output: 1.5999999999999999,
- cacheRead: 0.09999999999999999,
+ input: 0.4,
+ output: 1.6,
+ cacheRead: 0.1,
cacheWrite: 0,
},
contextWindow: 1047576,
@@ -1756,9 +1737,9 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.09999999999999999,
- output: 0.39999999999999997,
- cacheRead: 0.024999999999999998,
+ input: 0.1,
+ output: 0.4,
+ cacheRead: 0.025,
cacheWrite: 0,
},
contextWindow: 1047576,
@@ -1860,7 +1841,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 0.25,
output: 2,
- cacheRead: 0.024999999999999998,
+ cacheRead: 0.025,
cacheWrite: 0,
},
contextWindow: 400000,
@@ -1875,8 +1856,8 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.049999999999999996,
- output: 0.39999999999999997,
+ input: 0.05,
+ output: 0.4,
cacheRead: 0.005,
cacheWrite: 0,
},
@@ -1945,7 +1926,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 0.25,
output: 2,
- cacheRead: 0.024999999999999998,
+ cacheRead: 0.025,
cacheWrite: 0,
},
contextWindow: 400000,
@@ -2139,7 +2120,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
thinkingLevelMap: {"xhigh":"xhigh"},
input: ["text", "image"],
cost: {
- input: 0.19999999999999998,
+ input: 0.2,
output: 1.25,
cacheRead: 0.02,
cacheWrite: 0,
@@ -2227,8 +2208,8 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: true,
input: ["text"],
cost: {
- input: 0.049999999999999996,
- output: 0.19999999999999998,
+ input: 0.05,
+ output: 0.2,
cacheRead: 0,
cacheWrite: 0,
},
@@ -2388,6 +2369,23 @@ export const VERCEL_AI_GATEWAY_MODELS = {
contextWindow: 200000,
maxTokens: 8000,
} satisfies Model<"anthropic-messages">,
+ "sakana/fugu-ultra": {
+ id: "sakana/fugu-ultra",
+ name: "Fugu Ultra",
+ api: "anthropic-messages",
+ provider: "vercel-ai-gateway",
+ baseUrl: "https://ai-gateway.vercel.sh",
+ reasoning: true,
+ input: ["text", "image"],
+ cost: {
+ input: 5,
+ output: 30,
+ cacheRead: 0.5,
+ cacheWrite: 0,
+ },
+ contextWindow: 1000000,
+ maxTokens: 1000000,
+ } satisfies Model<"anthropic-messages">,
"stepfun/step-3.5-flash": {
id: "stepfun/step-3.5-flash",
name: "StepFun 3.5 Flash",
@@ -2399,8 +2397,8 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 0.09,
output: 0.3,
- cacheRead: 0,
- cacheWrite: 0.02,
+ cacheRead: 0.02,
+ cacheWrite: 0,
},
contextWindow: 262114,
maxTokens: 262114,
@@ -2414,7 +2412,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.19999999999999998,
+ input: 0.2,
output: 1.15,
cacheRead: 0.04,
cacheWrite: 0,
@@ -2431,9 +2429,9 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: false,
input: ["text", "image"],
cost: {
- input: 0.19999999999999998,
+ input: 0.2,
output: 0.5,
- cacheRead: 0.049999999999999996,
+ cacheRead: 0.05,
cacheWrite: 0,
},
contextWindow: 1000000,
@@ -2448,9 +2446,9 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: true,
input: ["text", "image"],
cost: {
- input: 0.19999999999999998,
+ input: 0.2,
output: 0.5,
- cacheRead: 0.049999999999999996,
+ cacheRead: 0.05,
cacheWrite: 0,
},
contextWindow: 1000000,
@@ -2467,7 +2465,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 1.25,
output: 2.5,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 2000000,
@@ -2484,7 +2482,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 1.25,
output: 2.5,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 2000000,
@@ -2501,7 +2499,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 1.25,
output: 2.5,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 2000000,
@@ -2518,7 +2516,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 1.25,
output: 2.5,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 2000000,
@@ -2535,7 +2533,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 1.25,
output: 2.5,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 2000000,
@@ -2552,7 +2550,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 1.25,
output: 2.5,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 2000000,
@@ -2569,7 +2567,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 1.25,
output: 2.5,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 1000000,
@@ -2586,7 +2584,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 1,
output: 2,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 256000,
@@ -2601,7 +2599,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: true,
input: ["text"],
cost: {
- input: 0.09999999999999999,
+ input: 0.1,
output: 0.3,
cacheRead: 0.01,
cacheWrite: 0,
@@ -2620,7 +2618,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
cost: {
input: 1,
output: 3,
- cacheRead: 0.19999999999999998,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 1000000,
@@ -2686,7 +2684,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
reasoning: true,
input: ["text"],
cost: {
- input: 0.19999999999999998,
+ input: 0.2,
output: 1.1,
cacheRead: 0.03,
cacheWrite: 0,
@@ -2704,7 +2702,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
input: ["text", "image"],
cost: {
input: 0.6,
- output: 1.7999999999999998,
+ output: 1.8,
cacheRead: 0.11,
cacheWrite: 0,
},
@@ -2738,8 +2736,8 @@ export const VERCEL_AI_GATEWAY_MODELS = {
input: ["text", "image"],
cost: {
input: 0.3,
- output: 0.8999999999999999,
- cacheRead: 0.049999999999999996,
+ output: 0.9,
+ cacheRead: 0.05,
cacheWrite: 0,
},
contextWindow: 128000,
@@ -2789,7 +2787,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
input: ["text"],
cost: {
input: 0.07,
- output: 0.39999999999999997,
+ output: 0.4,
cacheRead: 0,
cacheWrite: 0,
},
@@ -2806,7 +2804,7 @@ export const VERCEL_AI_GATEWAY_MODELS = {
input: ["text"],
cost: {
input: 0.06,
- output: 0.39999999999999997,
+ output: 0.4,
cacheRead: 0.01,
cacheWrite: 0,
},
@@ -2823,8 +2821,8 @@ export const VERCEL_AI_GATEWAY_MODELS = {
input: ["text"],
cost: {
input: 1,
- output: 3.1999999999999997,
- cacheRead: 0.19999999999999998,
+ output: 3.2,
+ cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 202800,
@@ -2864,6 +2862,23 @@ export const VERCEL_AI_GATEWAY_MODELS = {
contextWindow: 202800,
maxTokens: 64000,
} satisfies Model<"anthropic-messages">,
+ "zai/glm-5.2": {
+ id: "zai/glm-5.2",
+ name: "GLM 5.2",
+ api: "anthropic-messages",
+ provider: "vercel-ai-gateway",
+ baseUrl: "https://ai-gateway.vercel.sh",
+ reasoning: true,
+ input: ["text"],
+ cost: {
+ input: 1.5,
+ output: 4.5,
+ cacheRead: 0.3,
+ cacheWrite: 0,
+ },
+ contextWindow: 1000000,
+ maxTokens: 128000,
+ } satisfies Model<"anthropic-messages">,
"zai/glm-5v-turbo": {
id: "zai/glm-5v-turbo",
name: "GLM 5V Turbo",
diff --git a/packages/ai/src/providers/zai-coding-cn.models.ts b/packages/ai/src/providers/zai-coding-cn.models.ts
index 3f6cc35f..90865c0b 100644
--- a/packages/ai/src/providers/zai-coding-cn.models.ts
+++ b/packages/ai/src/providers/zai-coding-cn.models.ts
@@ -76,6 +76,25 @@ export const ZAI_CODING_CN_MODELS = {
contextWindow: 200000,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
+ "glm-5.2": {
+ id: "glm-5.2",
+ name: "GLM-5.2",
+ api: "openai-completions",
+ provider: "zai-coding-cn",
+ baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
+ compat: {"supportsDeveloperRole":false,"thinkingFormat":"zai","supportsReasoningEffort":true,"zaiToolStream":true},
+ reasoning: true,
+ thinkingLevelMap: {"minimal":null,"low":"high","medium":"high","high":"high","xhigh":"max"},
+ input: ["text"],
+ cost: {
+ input: 0,
+ output: 0,
+ cacheRead: 0,
+ cacheWrite: 0,
+ },
+ contextWindow: 1000000,
+ maxTokens: 131072,
+ } satisfies Model<"openai-completions">,
"glm-5v-turbo": {
id: "glm-5v-turbo",
name: "GLM-5V-Turbo",
diff --git a/packages/ai/src/providers/zai.models.ts b/packages/ai/src/providers/zai.models.ts
index b1da13f6..364a158f 100644
--- a/packages/ai/src/providers/zai.models.ts
+++ b/packages/ai/src/providers/zai.models.ts
@@ -76,6 +76,25 @@ export const ZAI_MODELS = {
contextWindow: 200000,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
+ "glm-5.2": {
+ id: "glm-5.2",
+ name: "GLM-5.2",
+ api: "openai-completions",
+ provider: "zai",
+ baseUrl: "https://api.z.ai/api/coding/paas/v4",
+ compat: {"supportsDeveloperRole":false,"thinkingFormat":"zai","supportsReasoningEffort":true,"zaiToolStream":true},
+ reasoning: true,
+ thinkingLevelMap: {"minimal":null,"low":"high","medium":"high","high":"high","xhigh":"max"},
+ input: ["text"],
+ cost: {
+ input: 0,
+ output: 0,
+ cacheRead: 0,
+ cacheWrite: 0,
+ },
+ contextWindow: 1000000,
+ maxTokens: 131072,
+ } satisfies Model<"openai-completions">,
"glm-5v-turbo": {
id: "glm-5v-turbo",
name: "GLM-5V-Turbo",
diff --git a/packages/ai/src/types.ts b/packages/ai/src/types.ts
index bbfbd5d6..48a6961e 100644
--- a/packages/ai/src/types.ts
+++ b/packages/ai/src/types.ts
@@ -74,6 +74,15 @@ export type ImagesProviderId = KnownImagesProvider | string;
export type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh";
export type ModelThinkingLevel = "off" | ThinkingLevel;
export type ThinkingLevelMap = Partial>;
+export type ChatTemplateKwargValue =
+ | string
+ | number
+ | boolean
+ | null
+ | {
+ $var: "thinking.enabled" | "thinking.effort";
+ omitWhenOff?: boolean;
+ };
/** Token budgets for each thinking level (token-based providers only) */
export interface ThinkingBudgets {
@@ -88,6 +97,9 @@ export type CacheRetention = "none" | "short" | "long";
export type Transport = "sse" | "websocket" | "websocket-cached" | "auto";
+/** Provider-scoped environment overrides. Values take precedence over process.env. */
+export type ProviderEnv = Record;
+
export interface ProviderResponse {
status: number;
headers: Record;
@@ -162,6 +174,12 @@ export interface StreamOptions {
* For example, Anthropic uses `user_id` for abuse tracking and rate limiting.
*/
metadata?: Record;
+ /**
+ * Provider-scoped environment values. These take precedence over process.env for
+ * provider configuration such as regional settings, endpoint placeholders, and
+ * proxy variables.
+ */
+ env?: ProviderEnv;
}
export type ProviderStreamOptions = StreamOptions & Record;
@@ -328,6 +346,8 @@ export interface Usage {
output: number;
cacheRead: number;
cacheWrite: number;
+ /** Subset of `cacheWrite` written with 1h retention. Only Anthropic reports this split. */
+ cacheWrite1h?: number;
totalTokens: number;
cost: {
input: number;
@@ -453,7 +473,7 @@ export interface OpenAICompletionsCompat {
requiresThinkingAsText?: boolean;
/** Whether all replayed assistant messages must include an empty reasoning_content field when reasoning is enabled. Default: auto-detected from URL. */
requiresReasoningContentOnAssistantMessages?: boolean;
- /** Format for reasoning/thinking parameter. "openai" uses reasoning_effort, "openrouter" uses reasoning: { effort }, "deepseek" uses thinking: { type } plus reasoning_effort when supported, "together" uses reasoning: { enabled } plus reasoning_effort when supported, "zai" uses thinking: { type }, "qwen" uses top-level enable_thinking: boolean, "qwen-chat-template" uses chat_template_kwargs.enable_thinking, "string-thinking" uses top-level thinking: string, and "ant-ling" uses reasoning: { effort } only when the mapped effort is non-null. Default: "openai". */
+ /** Format for reasoning/thinking parameter. "openai" uses reasoning_effort, "openrouter" uses reasoning: { effort }, "deepseek" uses thinking: { type } plus reasoning_effort when supported, "together" uses reasoning: { enabled } plus reasoning_effort when supported, "zai" uses thinking: { type }, "qwen" uses top-level enable_thinking: boolean, "qwen-chat-template" uses chat_template_kwargs.enable_thinking and preserve_thinking, "chat-template" uses configurable chat_template_kwargs, "string-thinking" uses top-level thinking: string, and "ant-ling" uses reasoning: { effort } only when the mapped effort is non-null. Default: "openai". */
thinkingFormat?:
| "openai"
| "openrouter"
@@ -461,9 +481,12 @@ export interface OpenAICompletionsCompat {
| "together"
| "zai"
| "qwen"
+ | "chat-template"
| "qwen-chat-template"
| "string-thinking"
| "ant-ling";
+ /** Kwargs to send as `chat_template_kwargs` when `thinkingFormat` is `chat-template`. Use `{ "$var": "thinking.enabled" }` or `{ "$var": "thinking.effort" }` for pi-controlled thinking values. */
+ chatTemplateKwargs?: Record;
/** OpenRouter-compatible routing preferences sent as the `provider` request field. */
openRouterRouting?: OpenRouterRouting;
/** Vercel AI Gateway routing preferences. Only used when baseUrl points to Vercel AI Gateway. */
diff --git a/packages/ai/src/utils/node-http-proxy.ts b/packages/ai/src/utils/node-http-proxy.ts
index 7b5f4750..81ff0089 100644
--- a/packages/ai/src/utils/node-http-proxy.ts
+++ b/packages/ai/src/utils/node-http-proxy.ts
@@ -1,7 +1,5 @@
-import type { Agent as HttpAgent } from "node:http";
-import type { Agent as HttpsAgent } from "node:https";
-import { HttpProxyAgent } from "http-proxy-agent";
-import { HttpsProxyAgent } from "https-proxy-agent";
+import type { ProviderEnv } from "../types.ts";
+import { getProviderEnvValue } from "./provider-env.ts";
const DEFAULT_PROXY_PORTS: Record = {
ftp: 21,
@@ -12,16 +10,16 @@ const DEFAULT_PROXY_PORTS: Record = {
wss: 443,
};
-export interface NodeHttpProxyAgents {
- httpAgent: HttpAgent;
- httpsAgent: HttpsAgent;
-}
-
-export const UNSUPPORTED_PROXY_PROTOCOL_MESSAGE =
- "Unsupported proxy protocol. SOCKS and PAC proxy URLs are not supported; use an HTTP or HTTPS proxy URL.";
-
-function getProxyEnv(key: string): string {
- return process.env[key.toLowerCase()] || process.env[key.toUpperCase()] || "";
+function getProxyEnv(key: string, env?: ProviderEnv): string {
+ const lowercaseKey = key.toLowerCase();
+ const uppercaseKey = key.toUpperCase();
+ return (
+ env?.[lowercaseKey] ||
+ env?.[uppercaseKey] ||
+ getProviderEnvValue(lowercaseKey) ||
+ getProviderEnvValue(uppercaseKey) ||
+ ""
+ );
}
function parseProxyTargetUrl(targetUrl: string | URL): URL | undefined {
@@ -36,8 +34,8 @@ function parseProxyTargetUrl(targetUrl: string | URL): URL | undefined {
}
}
-function shouldProxyHostname(hostname: string, port: number): boolean {
- const noProxy = getProxyEnv("no_proxy").toLowerCase();
+function shouldProxyHostname(hostname: string, port: number, env?: ProviderEnv): boolean {
+ const noProxy = getProxyEnv("no_proxy", env).toLowerCase();
if (!noProxy) {
return true;
}
@@ -68,7 +66,7 @@ function shouldProxyHostname(hostname: string, port: number): boolean {
});
}
-function getProxyForUrl(targetUrl: string | URL): string {
+function getProxyForUrl(targetUrl: string | URL, env?: ProviderEnv): string {
const parsedUrl = parseProxyTargetUrl(targetUrl);
if (!parsedUrl?.protocol || !parsedUrl.host) {
return "";
@@ -77,19 +75,22 @@ function getProxyForUrl(targetUrl: string | URL): string {
const protocol = parsedUrl.protocol.split(":", 1)[0]!;
const hostname = parsedUrl.host.replace(/:\d*$/, "");
const port = Number.parseInt(parsedUrl.port, 10) || DEFAULT_PROXY_PORTS[protocol] || 0;
- if (!shouldProxyHostname(hostname, port)) {
+ if (!shouldProxyHostname(hostname, port, env)) {
return "";
}
- let proxy = getProxyEnv(`${protocol}_proxy`) || getProxyEnv("all_proxy");
+ let proxy = getProxyEnv(`${protocol}_proxy`, env) || getProxyEnv("all_proxy", env);
if (proxy && !proxy.includes("://")) {
proxy = `${protocol}://${proxy}`;
}
return proxy;
}
-export function resolveHttpProxyUrlForTarget(targetUrl: string | URL): URL | undefined {
- const proxy = getProxyForUrl(targetUrl);
+export const UNSUPPORTED_PROXY_PROTOCOL_MESSAGE =
+ "Unsupported proxy protocol. SOCKS and PAC proxy URLs are not supported; use an HTTP or HTTPS proxy URL.";
+
+export function resolveHttpProxyUrlForTarget(targetUrl: string | URL, env?: ProviderEnv): URL | undefined {
+ const proxy = getProxyForUrl(targetUrl, env);
if (!proxy) {
return undefined;
}
@@ -109,15 +110,3 @@ export function resolveHttpProxyUrlForTarget(targetUrl: string | URL): URL | und
return proxyUrl;
}
-
-export function createHttpProxyAgentsForTarget(targetUrl: string | URL): NodeHttpProxyAgents | undefined {
- const proxyUrl = resolveHttpProxyUrlForTarget(targetUrl);
- if (!proxyUrl) {
- return undefined;
- }
-
- return {
- httpAgent: new HttpProxyAgent(proxyUrl),
- httpsAgent: new HttpsProxyAgent(proxyUrl) as unknown as HttpsAgent,
- };
-}
diff --git a/packages/ai/src/utils/oauth/anthropic.ts b/packages/ai/src/utils/oauth/anthropic.ts
index c8a9226a..591e9cde 100644
--- a/packages/ai/src/utils/oauth/anthropic.ts
+++ b/packages/ai/src/utils/oauth/anthropic.ts
@@ -7,6 +7,7 @@
import type { Server } from "node:http";
import type { OAuthAuth } from "../../auth/types.ts";
+import { getProviderEnvValue } from "../provider-env.ts";
import { oauthErrorHtml, oauthSuccessHtml } from "./oauth-page.ts";
import { generatePKCE } from "./pkce.ts";
import type { OAuthCredentials, OAuthLoginCallbacks, OAuthPrompt, OAuthProviderInterface } from "./types.ts";
@@ -29,7 +30,7 @@ const decode = (s: string) => atob(s);
const CLIENT_ID = decode("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
const AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
const TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
-const CALLBACK_HOST = process.env.PI_OAUTH_CALLBACK_HOST || "127.0.0.1";
+const CALLBACK_HOST = getProviderEnvValue("PI_OAUTH_CALLBACK_HOST") || "127.0.0.1";
const CALLBACK_PORT = 53692;
const CALLBACK_PATH = "/callback";
const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
diff --git a/packages/ai/src/utils/oauth/github-copilot.ts b/packages/ai/src/utils/oauth/github-copilot.ts
index e63741a8..111af0ad 100644
--- a/packages/ai/src/utils/oauth/github-copilot.ts
+++ b/packages/ai/src/utils/oauth/github-copilot.ts
@@ -10,6 +10,7 @@ import type { OAuthCredentials, OAuthDeviceCodeInfo, OAuthLoginCallbacks, OAuthP
type CopilotCredentials = OAuthCredentials & {
enterpriseUrl?: string;
+ availableModelIds: string[];
};
const decode = (s: string) => atob(s);
@@ -21,6 +22,7 @@ const COPILOT_HEADERS = {
"Editor-Plugin-Version": "copilot-chat/0.35.0",
"Copilot-Integration-Id": "vscode-chat",
} as const;
+const COPILOT_API_VERSION = "2026-06-01";
type DeviceCodeResponse = {
device_code: string;
@@ -89,6 +91,48 @@ export function getGitHubCopilotBaseUrl(token?: string, enterpriseDomain?: strin
return "https://api.individual.githubcopilot.com";
}
+function asRecord(value: unknown): Record | undefined {
+ return value && typeof value === "object" ? (value as Record) : undefined;
+}
+
+function isSelectableCopilotModel(item: Record): boolean {
+ const policy = asRecord(item.policy);
+ const capabilities = asRecord(item.capabilities);
+ const supports = asRecord(capabilities?.supports);
+ return item.model_picker_enabled === true && policy?.state !== "disabled" && supports?.tool_calls !== false;
+}
+
+function parseAvailableCopilotModelIds(raw: unknown): string[] {
+ const data = asRecord(raw)?.data;
+ if (!Array.isArray(data)) {
+ throw new Error("Invalid Copilot models response");
+ }
+
+ const ids: string[] = [];
+ for (const rawItem of data) {
+ const item = asRecord(rawItem);
+ const id = item?.id;
+ if (typeof id === "string" && item && isSelectableCopilotModel(item)) {
+ ids.push(id);
+ }
+ }
+ return ids;
+}
+
+async function fetchAvailableGitHubCopilotModelIds(copilotToken: string, enterpriseDomain?: string): Promise {
+ const baseUrl = getGitHubCopilotBaseUrl(copilotToken, enterpriseDomain);
+ const raw = await fetchJson(`${baseUrl}/models`, {
+ headers: {
+ Accept: "application/json",
+ Authorization: `Bearer ${copilotToken}`,
+ ...COPILOT_HEADERS,
+ "X-GitHub-Api-Version": COPILOT_API_VERSION,
+ },
+ signal: AbortSignal.timeout(5000),
+ });
+ return parseAvailableCopilotModelIds(raw);
+}
+
async function fetchJson(url: string, init: RequestInit): Promise {
const response = await fetch(url, init);
if (!response.ok) {
@@ -202,10 +246,7 @@ async function pollForGitHubAccessToken(
});
}
-/**
- * Refresh GitHub Copilot token
- */
-export async function refreshGitHubCopilotToken(
+async function refreshGitHubCopilotAccessToken(
refreshToken: string,
enterpriseDomain?: string,
): Promise {
@@ -239,6 +280,20 @@ export async function refreshGitHubCopilotToken(
};
}
+/**
+ * Refresh GitHub Copilot token
+ */
+export async function refreshGitHubCopilotToken(
+ refreshToken: string,
+ enterpriseDomain?: string,
+): Promise {
+ const credentials = await refreshGitHubCopilotAccessToken(refreshToken, enterpriseDomain);
+ return {
+ ...credentials,
+ availableModelIds: await fetchAvailableGitHubCopilotModelIds(credentials.access, enterpriseDomain),
+ };
+}
+
/**
* Enable a model for the user's GitHub Copilot account.
* This is required for some models (like Claude, Grok) before they can be used.
@@ -323,12 +378,18 @@ export async function loginGitHubCopilot(options: {
});
const githubAccessToken = await pollForGitHubAccessToken(domain, device, options.signal);
- const credentials = await refreshGitHubCopilotToken(githubAccessToken, enterpriseDomain ?? undefined);
+ const credentials = await refreshGitHubCopilotAccessToken(githubAccessToken, enterpriseDomain ?? undefined);
// Enable all models after successful login
options.onProgress?.("Enabling models...");
await enableAllGitHubCopilotModels(credentials.access, enterpriseDomain ?? undefined);
- return credentials;
+
+ // Fetch availability after policy enable so newly enabled models are included,
+ // while unavailable models are still filtered out.
+ return {
+ ...credentials,
+ availableModelIds: await fetchAvailableGitHubCopilotModelIds(credentials.access, enterpriseDomain ?? undefined),
+ };
}
function copilotEnterpriseDomain(credential: OAuthCredential): string | undefined {
@@ -393,6 +454,14 @@ export const githubCopilotOAuthProvider: OAuthProviderInterface = {
const creds = credentials as CopilotCredentials;
const domain = creds.enterpriseUrl ? (normalizeDomain(creds.enterpriseUrl) ?? undefined) : undefined;
const baseUrl = getGitHubCopilotBaseUrl(creds.access, domain);
- return models.map((m) => (m.provider === "github-copilot" ? { ...m, baseUrl } : m));
+ // Older stored Pi auth entries do not have account-specific model IDs yet;
+ // keep their existing generated-catalog behavior until the next refresh/login.
+ const availableModelIds = "availableModelIds" in creds ? new Set(creds.availableModelIds) : undefined;
+
+ return models.flatMap((m) => {
+ if (m.provider !== "github-copilot") return [m];
+ if (availableModelIds && !availableModelIds.has(m.id)) return [];
+ return [{ ...m, baseUrl }];
+ });
},
};
diff --git a/packages/ai/src/utils/oauth/openai-codex.ts b/packages/ai/src/utils/oauth/openai-codex.ts
index ae9cc9f9..a2f7cd00 100644
--- a/packages/ai/src/utils/oauth/openai-codex.ts
+++ b/packages/ai/src/utils/oauth/openai-codex.ts
@@ -18,6 +18,7 @@ if (typeof process !== "undefined" && (process.versions?.node || process.version
}
import type { OAuthAuth } from "../../auth/types.ts";
+import { getProviderEnvValue } from "../provider-env.ts";
import { pollOAuthDeviceCodeFlow } from "./device-code.ts";
import { oauthErrorHtml, oauthSuccessHtml } from "./oauth-page.ts";
import { generatePKCE } from "./pkce.ts";
@@ -48,7 +49,7 @@ type OAuthToken = { access: string; refresh: string; expires: number };
type TokenOperation = "exchange" | "refresh";
function getCallbackHost(): string {
- return typeof process !== "undefined" ? process.env.PI_OAUTH_CALLBACK_HOST || "127.0.0.1" : "127.0.0.1";
+ return getProviderEnvValue("PI_OAUTH_CALLBACK_HOST") || "127.0.0.1";
}
type DeviceAuthInfo = {
diff --git a/packages/ai/src/utils/overflow.ts b/packages/ai/src/utils/overflow.ts
index e3a9b37a..623b873a 100644
--- a/packages/ai/src/utils/overflow.ts
+++ b/packages/ai/src/utils/overflow.ts
@@ -12,6 +12,7 @@ import type { AssistantMessage } from "../types.ts";
* - Anthropic: "413 {\"error\":{\"type\":\"request_too_large\",\"message\":\"Request exceeds the maximum size\"}}"
* - OpenAI: "Your input exceeds the context window of this model"
* - OpenAI/LiteLLM: "Requested token count exceeds the model's maximum context length of 131072 tokens"
+ * - OpenAI-compatible: "Input length (265330) exceeds model's maximum context length (262144)."
* - Google: "The input token count (1196265) exceeds the maximum number of tokens allowed (1048575)"
* - xAI: "This model's maximum prompt length is 131072 but the request contains 537812 tokens"
* - Groq: "Please reduce the length of the messages or completion"
@@ -36,7 +37,7 @@ const OVERFLOW_PATTERNS = [
/request_too_large/i, // Anthropic request byte-size overflow (HTTP 413)
/input is too long for requested model/i, // Amazon Bedrock
/exceeds the context window/i, // OpenAI (Completions & Responses API)
- /exceeds (?:the )?(?:model'?s )?maximum context length of [\d,]+ tokens?/i, // OpenAI-compatible proxies (LiteLLM)
+ /exceeds (?:the )?(?:model'?s )?maximum context length(?: of [\d,]+ tokens?|\s*\([\d,]+\))/i, // OpenAI-compatible proxies (LiteLLM)
/input token count.*exceeds the maximum/i, // Google (Gemini)
/maximum prompt length is \d+/i, // xAI (Grok)
/reduce the length of the messages/i, // Groq
@@ -85,7 +86,7 @@ const NON_OVERFLOW_PATTERNS = [
*
* **Reliable detection (returns error with detectable message):**
* - Anthropic: "prompt is too long: X tokens > Y maximum" or "request_too_large"
- * - OpenAI (Completions & Responses): "exceeds the context window" or "exceeds the model's maximum context length of X tokens"
+ * - OpenAI (Completions & Responses): "exceeds the context window", "exceeds the model's maximum context length of X tokens", or "exceeds model's maximum context length (X)"
* - Google Gemini: "input token count exceeds the maximum"
* - xAI (Grok): "maximum prompt length is X but request contains Y"
* - Groq: "reduce the length of the messages"
diff --git a/packages/ai/src/utils/provider-env.ts b/packages/ai/src/utils/provider-env.ts
new file mode 100644
index 00000000..db496067
--- /dev/null
+++ b/packages/ai/src/utils/provider-env.ts
@@ -0,0 +1,52 @@
+import type { ProviderEnv } from "../types.ts";
+
+let procEnvCache: Map | null = null;
+
+/**
+ * Fallback for https://github.com/oven-sh/bun/issues/27802.
+ * Bun compiled binaries can expose an empty process.env inside Linux sandboxes
+ * even though /proc/self/environ contains the environment.
+ *
+ * This intentionally duplicates restoreSandboxEnv() in
+ * packages/coding-agent/src/bun/restore-sandbox-env.ts. The ai package can be
+ * used directly, without going through that entrypoint, so provider env lookup
+ * must not depend on process.env having been patched.
+ */
+function getBunSandboxEnvValue(name: string): string | undefined {
+ if (typeof process === "undefined" || !process.versions?.bun || Object.keys(process.env).length > 0) {
+ return undefined;
+ }
+
+ if (procEnvCache === null) {
+ procEnvCache = new Map();
+ try {
+ const { readFileSync } = require("node:fs") as {
+ readFileSync(path: string, encoding: BufferEncoding): string;
+ };
+ const data = readFileSync("/proc/self/environ", "utf-8");
+ for (const entry of data.split("\0")) {
+ const idx = entry.indexOf("=");
+ if (idx > 0) {
+ procEnvCache.set(entry.slice(0, idx), entry.slice(idx + 1));
+ }
+ }
+ } catch {
+ // /proc/self/environ may not exist or may not be readable.
+ }
+ }
+
+ return procEnvCache.get(name);
+}
+
+/**
+ * Resolve a provider env value from scoped overrides, normal process.env, then
+ * the duplicated Bun sandbox fallback for direct pi-ai consumers.
+ */
+export function getProviderEnvValue(name: string, env?: ProviderEnv): string | undefined {
+ return (
+ env?.[name] ||
+ (typeof process !== "undefined" ? process.env[name] : undefined) ||
+ getBunSandboxEnvValue(name) ||
+ undefined
+ );
+}
diff --git a/packages/ai/test/anthropic-adaptive-thinking-models.test.ts b/packages/ai/test/anthropic-adaptive-thinking-models.test.ts
index e3cf0601..8d99ff18 100644
--- a/packages/ai/test/anthropic-adaptive-thinking-models.test.ts
+++ b/packages/ai/test/anthropic-adaptive-thinking-models.test.ts
@@ -5,9 +5,8 @@ import type { Api, Model } from "../src/types.ts";
const EXPECTED_CURRENT_ADAPTIVE_THINKING_MODELS = [
"anthropic/claude-fable-5",
"anthropic/claude-opus-4-8",
- "opencode/claude-fable-5",
+ "cloudflare-ai-gateway/claude-fable-5",
"opencode/claude-opus-4-8",
- "vercel-ai-gateway/anthropic/claude-fable-5",
"vercel-ai-gateway/anthropic/claude-opus-4.8",
];
diff --git a/packages/ai/test/anthropic-cache-write-1h-cost.test.ts b/packages/ai/test/anthropic-cache-write-1h-cost.test.ts
new file mode 100644
index 00000000..13745e9b
--- /dev/null
+++ b/packages/ai/test/anthropic-cache-write-1h-cost.test.ts
@@ -0,0 +1,86 @@
+import type Anthropic from "@anthropic-ai/sdk";
+import { describe, expect, it } from "vitest";
+import { stream as streamAnthropic } from "../src/api/anthropic-messages.ts";
+import { getModel } from "../src/compat.ts";
+import type { Context } from "../src/types.ts";
+
+function createSseResponse(events: Array<{ event: string; data: string }>): Response {
+ const body = events.map(({ event, data }) => `event: ${event}\ndata: ${data}\n`).join("\n");
+ return new Response(body, { status: 200, headers: { "content-type": "text/event-stream" } });
+}
+
+function createFakeAnthropicClient(response: Response): Anthropic {
+ return {
+ messages: { create: () => ({ asResponse: async () => response }) },
+ } as unknown as Anthropic;
+}
+
+function eventsWithCacheCreation(
+ cacheCreation: Record | undefined,
+): Array<{ event: string; data: string }> {
+ const startUsage: Record = {
+ input_tokens: 100,
+ output_tokens: 0,
+ cache_read_input_tokens: 0,
+ cache_creation_input_tokens: 1_000_000,
+ };
+ if (cacheCreation) startUsage.cache_creation = cacheCreation;
+ return [
+ {
+ event: "message_start",
+ data: JSON.stringify({ type: "message_start", message: { id: "msg_test", usage: startUsage } }),
+ },
+ {
+ event: "content_block_start",
+ data: JSON.stringify({ type: "content_block_start", index: 0, content_block: { type: "text", text: "" } }),
+ },
+ {
+ event: "content_block_delta",
+ data: JSON.stringify({ type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "Hi" } }),
+ },
+ { event: "content_block_stop", data: JSON.stringify({ type: "content_block_stop", index: 0 }) },
+ {
+ event: "message_delta",
+ data: JSON.stringify({
+ type: "message_delta",
+ delta: { stop_reason: "end_turn" },
+ usage: {
+ input_tokens: 100,
+ output_tokens: 5,
+ cache_read_input_tokens: 0,
+ cache_creation_input_tokens: 1_000_000,
+ },
+ }),
+ },
+ { event: "message_stop", data: JSON.stringify({ type: "message_stop" }) },
+ ];
+}
+
+// claude-opus-4-8: input 5, cacheWrite (5m) 6.25 per Mtok. 1h write = 2x input = 10.
+const context: Context = { messages: [{ role: "user", content: "hi", timestamp: Date.now() }] };
+
+describe("Anthropic 1h cache write cost", () => {
+ it("prices the 1h portion at 2x input and the rest at the 5m rate", async () => {
+ const model = getModel("anthropic", "claude-opus-4-8");
+ const response = createSseResponse(
+ eventsWithCacheCreation({ ephemeral_5m_input_tokens: 600_000, ephemeral_1h_input_tokens: 400_000 }),
+ );
+ const result = await streamAnthropic(model, context, { client: createFakeAnthropicClient(response) }).result();
+
+ expect(result.usage.cacheWrite).toBe(1_000_000);
+ expect(result.usage.cacheWrite1h).toBe(400_000);
+ // 600k * 6.25/Mtok + 400k * 10/Mtok = 3.75 + 4.0 = 7.75
+ expect(result.usage.cost.cacheWrite).toBeCloseTo(7.75, 10);
+ });
+
+ it("falls back to the 5m rate when no breakdown is reported", async () => {
+ const model = getModel("anthropic", "claude-opus-4-8");
+ const response = createSseResponse(eventsWithCacheCreation(undefined));
+ const result = await streamAnthropic(model, context, { client: createFakeAnthropicClient(response) }).result();
+
+ expect(result.usage.cacheWrite).toBe(1_000_000);
+ expect(result.usage.cacheWrite1h ?? 0).toBe(0);
+ // 1M * 6.25/Mtok = 6.25
+ expect(result.usage.cost.cacheWrite).toBeCloseTo(6.25, 10);
+ });
+});
diff --git a/packages/ai/test/anthropic-sse-parsing.test.ts b/packages/ai/test/anthropic-sse-parsing.test.ts
index 0ed58b81..e510ec55 100644
--- a/packages/ai/test/anthropic-sse-parsing.test.ts
+++ b/packages/ai/test/anthropic-sse-parsing.test.ts
@@ -166,6 +166,64 @@ describe("Anthropic raw SSE parsing", () => {
});
});
+ it("preserves refusal stop details from message_delta", async () => {
+ const model = getModel("anthropic", "claude-fable-5");
+ const context: Context = {
+ messages: [{ role: "user", content: "blocked request", timestamp: Date.now() }],
+ };
+ const explanation =
+ "This request triggered restrictions on violative cyber content and was blocked under Anthropic's Usage Policy. To learn more, provide feedback, or request an exemption based on how you use Claude, visit our help center: https://support.claude.com/en/articles/14604842-real-time-cyber-safeguards-on-claude.";
+ const response = createSseResponse([
+ {
+ event: "message_start",
+ data: JSON.stringify({
+ type: "message_start",
+ message: {
+ id: "msg_01XFUDYJgAACzvnptvVoYEL",
+ usage: {
+ input_tokens: 412,
+ output_tokens: 0,
+ cache_read_input_tokens: 0,
+ cache_creation_input_tokens: 0,
+ },
+ },
+ }),
+ },
+ {
+ event: "message_delta",
+ data: JSON.stringify({
+ type: "message_delta",
+ delta: {
+ stop_reason: "refusal",
+ stop_details: {
+ type: "refusal",
+ category: "cyber",
+ explanation,
+ },
+ },
+ usage: {
+ input_tokens: 412,
+ output_tokens: 0,
+ cache_read_input_tokens: 0,
+ cache_creation_input_tokens: 0,
+ },
+ }),
+ },
+ {
+ event: "message_stop",
+ data: JSON.stringify({ type: "message_stop" }),
+ },
+ ]);
+
+ const stream = streamAnthropic(model, context, {
+ client: createFakeAnthropicClient(response),
+ });
+ const result = await stream.result();
+
+ expect(result.stopReason).toBe("error");
+ expect(result.errorMessage).toBe(explanation);
+ });
+
it("ignores unknown SSE events after message_stop", async () => {
const model = getModel("anthropic", "claude-haiku-4-5");
const context: Context = {
diff --git a/packages/ai/test/bedrock-endpoint-resolution.test.ts b/packages/ai/test/bedrock-endpoint-resolution.test.ts
index db2ae04b..168cf4d1 100644
--- a/packages/ai/test/bedrock-endpoint-resolution.test.ts
+++ b/packages/ai/test/bedrock-endpoint-resolution.test.ts
@@ -44,7 +44,7 @@ vi.mock("@aws-sdk/client-bedrock-runtime", () => {
};
});
-import { stream as streamBedrock } from "../src/api/bedrock-converse-stream.ts";
+import { type BedrockOptions, stream as streamBedrock } from "../src/api/bedrock-converse-stream.ts";
import { getModel } from "../src/compat.ts";
import type { Context, Model } from "../src/types.ts";
@@ -83,8 +83,12 @@ afterEach(() => {
}
});
-async function captureClientConfig(model: Model<"bedrock-converse-stream">): Promise> {
- await streamBedrock(model, context, { cacheRetention: "none" }).result();
+async function captureClientConfig(
+ model: Model<"bedrock-converse-stream">,
+ options: BedrockOptions = {},
+): Promise> {
+ bedrockMock.constructorCalls.length = 0;
+ await streamBedrock(model, context, { cacheRetention: "none", ...options }).result();
expect(bedrockMock.constructorCalls).toHaveLength(1);
return bedrockMock.constructorCalls[0];
}
@@ -115,6 +119,29 @@ describe("bedrock endpoint resolution", () => {
expect(config.region).toBe("eu-central-1");
});
+ it("handles missing regions for explicit, scoped, and ambient profiles", async () => {
+ const model = getModel("amazon-bedrock", "eu.anthropic.claude-sonnet-4-5-20250929-v1:0");
+
+ let config = await captureClientConfig(model, { profile: "bedrock-profile" });
+
+ expect(config.profile).toBe("bedrock-profile");
+ expect(config.endpoint).toBe("https://bedrock-runtime.eu-central-1.amazonaws.com");
+ expect(config.region).toBe("eu-central-1");
+
+ config = await captureClientConfig(model, { env: { AWS_PROFILE: "scoped-bedrock-profile" } });
+
+ expect(config.profile).toBe("scoped-bedrock-profile");
+ expect(config.endpoint).toBe("https://bedrock-runtime.eu-central-1.amazonaws.com");
+ expect(config.region).toBe("eu-central-1");
+
+ process.env.AWS_PROFILE = "ambient-bedrock-profile";
+ config = await captureClientConfig(model);
+
+ expect(config.profile).toBe("ambient-bedrock-profile");
+ expect(config.endpoint).toBeUndefined();
+ expect(config.region).toBeUndefined();
+ });
+
it("still passes custom Bedrock endpoints through to the SDK client", async () => {
process.env.AWS_REGION = "us-west-2";
const baseModel = getModel("amazon-bedrock", "us.anthropic.claude-opus-4-8");
diff --git a/packages/ai/test/cache-retention.test.ts b/packages/ai/test/cache-retention.test.ts
index 1296cebd..c80ad19c 100644
--- a/packages/ai/test/cache-retention.test.ts
+++ b/packages/ai/test/cache-retention.test.ts
@@ -3,6 +3,7 @@ import { stream as streamAnthropic } from "../src/api/anthropic-messages.ts";
import { stream as streamOpenAICompletions } from "../src/api/openai-completions.ts";
import { stream as streamOpenAIResponses } from "../src/api/openai-responses.ts";
import { getModel, stream } from "../src/compat.ts";
+import { MODELS } from "../src/models.generated.ts";
import type { Context, Model } from "../src/types.ts";
class PayloadCaptured extends Error {
@@ -12,6 +13,11 @@ class PayloadCaptured extends Error {
}
}
+interface OpenAICompletionsCachePayload {
+ prompt_cache_key?: string;
+ prompt_cache_retention?: string;
+}
+
function stopAfterPayload(capture: (payload: TPayload) => void): (payload: unknown) => never {
return (payload: unknown): never => {
capture(payload as TPayload);
@@ -454,5 +460,39 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
expect(capturedPayload.prompt_cache_key).toBeUndefined();
expect(capturedPayload.prompt_cache_retention).toBeUndefined();
});
+
+ it.each([
+ MODELS.opencode["deepseek-v4-flash"],
+ MODELS.opencode["deepseek-v4-pro"],
+ MODELS.opencode["kimi-k2.5"],
+ MODELS.opencode["kimi-k2.6"],
+ MODELS.opencode["minimax-m2.7"],
+ MODELS["opencode-go"]["kimi-k2.6"],
+ ] as const)("should omit long cache retention for $provider/$id", async (metadata) => {
+ const model = metadata as Model<"openai-completions">;
+ let capturedPayload: OpenAICompletionsCachePayload | undefined;
+
+ try {
+ const s = streamOpenAICompletions(model, context, {
+ apiKey: "fake-key",
+ cacheRetention: "long",
+ sessionId: "session-opencode-long-cache-unsupported",
+ onPayload: stopAfterPayload((payload) => {
+ capturedPayload = payload;
+ }),
+ });
+
+ for await (const event of s) {
+ if (event.type === "error") break;
+ }
+ } catch {
+ // Expected to fail
+ }
+
+ expect(model.compat?.supportsLongCacheRetention).toBe(false);
+ expect(capturedPayload).toBeDefined();
+ expect(capturedPayload?.prompt_cache_key).toBeUndefined();
+ expect(capturedPayload?.prompt_cache_retention).toBeUndefined();
+ });
});
});
diff --git a/packages/ai/test/github-copilot-anthropic.test.ts b/packages/ai/test/github-copilot-anthropic.test.ts
index 7900d22e..74a95418 100644
--- a/packages/ai/test/github-copilot-anthropic.test.ts
+++ b/packages/ai/test/github-copilot-anthropic.test.ts
@@ -1,6 +1,7 @@
import { describe, expect, it, vi } from "vitest";
import { stream as streamAnthropic } from "../src/api/anthropic-messages.ts";
import { getModel } from "../src/compat.ts";
+import { getSupportedThinkingLevels } from "../src/models.ts";
import type { Context } from "../src/types.ts";
const mockState = vi.hoisted(() => ({
@@ -54,6 +55,16 @@ describe("Copilot Claude via Anthropic Messages", () => {
messages: [{ role: "user", content: "Hello", timestamp: Date.now() }],
};
+ it("applies Copilot-specific adaptive thinking effort overrides", () => {
+ const opus47 = getModel("github-copilot", "claude-opus-4.7");
+ expect(opus47.thinkingLevelMap).toMatchObject({ minimal: "low", xhigh: "xhigh" });
+ expect(getSupportedThinkingLevels(opus47)).toContain("xhigh");
+
+ const sonnet46 = getModel("github-copilot", "claude-sonnet-4.6");
+ expect(sonnet46.thinkingLevelMap).toMatchObject({ minimal: "low", xhigh: "max" });
+ expect(getSupportedThinkingLevels(sonnet46)).toContain("xhigh");
+ });
+
it("uses Bearer auth, Copilot headers, and valid Anthropic Messages payload", async () => {
const model = getModel("github-copilot", "claude-sonnet-4.6");
expect(model.api).toBe("anthropic-messages");
diff --git a/packages/ai/test/github-copilot-oauth.test.ts b/packages/ai/test/github-copilot-oauth.test.ts
index c94370da..f5e426e3 100644
--- a/packages/ai/test/github-copilot-oauth.test.ts
+++ b/packages/ai/test/github-copilot-oauth.test.ts
@@ -1,5 +1,10 @@
import { afterEach, describe, expect, it, vi } from "vitest";
-import { loginGitHubCopilot } from "../src/utils/oauth/github-copilot.ts";
+import { getModels } from "../src/compat.ts";
+import {
+ githubCopilotOAuthProvider,
+ loginGitHubCopilot,
+ refreshGitHubCopilotToken,
+} from "../src/utils/oauth/github-copilot.ts";
function jsonResponse(body: unknown, status: number = 200): Response {
return new Response(JSON.stringify(body), {
@@ -29,6 +34,57 @@ describe("GitHub Copilot OAuth device flow", () => {
vi.useRealTimers();
});
+ it("filters models to the authenticated account picker catalog", async () => {
+ const fetchMock = vi.fn(async (input: unknown, init?: RequestInit): Promise => {
+ const url = getUrl(input);
+
+ if (url.includes("/copilot_internal/v2/token")) {
+ return jsonResponse({
+ token: "tid=test;exp=9999999999;proxy-ep=proxy.individual.githubcopilot.com;",
+ expires_at: 9999999999,
+ });
+ }
+
+ if (url === "https://api.individual.githubcopilot.com/models") {
+ expect(init?.headers).toMatchObject({
+ Authorization: "Bearer tid=test;exp=9999999999;proxy-ep=proxy.individual.githubcopilot.com;",
+ });
+ return jsonResponse({
+ data: [
+ {
+ id: "gpt-4.1",
+ model_picker_enabled: true,
+ capabilities: { supports: { tool_calls: true } },
+ },
+ {
+ id: "claude-opus-4.7",
+ model_picker_enabled: true,
+ policy: { state: "disabled" },
+ capabilities: { supports: { tool_calls: true } },
+ },
+ {
+ id: "gpt-5.4-nano",
+ model_picker_enabled: false,
+ capabilities: { supports: { tool_calls: true } },
+ },
+ ],
+ });
+ }
+
+ throw new Error(`Unexpected fetch URL: ${url}`);
+ });
+
+ vi.stubGlobal("fetch", fetchMock);
+
+ const credentials = await refreshGitHubCopilotToken("ghu_refresh_token");
+ expect(credentials.availableModelIds).toEqual(["gpt-4.1"]);
+
+ const modifiedModels = githubCopilotOAuthProvider.modifyModels?.(getModels("github-copilot"), credentials) ?? [];
+ expect(modifiedModels.filter((model) => model.provider === "github-copilot").map((model) => model.id)).toEqual([
+ "gpt-4.1",
+ ]);
+ });
+
it("reports device-code details through onDeviceCode", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-03-09T00:00:00Z"));
@@ -57,6 +113,10 @@ describe("GitHub Copilot OAuth device flow", () => {
});
}
+ if (url.endsWith("/models")) {
+ return jsonResponse({ data: [] });
+ }
+
if (url.includes("/models/") && url.endsWith("/policy")) {
return new Response("", { status: 200 });
}
@@ -146,6 +206,10 @@ describe("GitHub Copilot OAuth device flow", () => {
});
}
+ if (url.endsWith("/models")) {
+ return jsonResponse({ data: [] });
+ }
+
if (url.includes("/models/") && url.endsWith("/policy")) {
return new Response("", { status: 200 });
}
@@ -231,6 +295,10 @@ describe("GitHub Copilot OAuth device flow", () => {
});
}
+ if (url.endsWith("/models")) {
+ return jsonResponse({ data: [] });
+ }
+
if (url.includes("/models/") && url.endsWith("/policy")) {
return new Response("", { status: 200 });
}
diff --git a/packages/ai/test/mistral-reasoning-mode.test.ts b/packages/ai/test/mistral-reasoning-mode.test.ts
index 4bd24f79..bffa292d 100644
--- a/packages/ai/test/mistral-reasoning-mode.test.ts
+++ b/packages/ai/test/mistral-reasoning-mode.test.ts
@@ -5,6 +5,7 @@ import type { Context, Model, SimpleStreamOptions } from "../src/types.ts";
interface MistralPayload {
promptMode?: "reasoning";
reasoningEffort?: "none" | "high";
+ promptCacheKey?: string;
}
function makeContext(): Context {
@@ -76,4 +77,21 @@ describe("Mistral reasoning mode selection", () => {
expect(payload.reasoningEffort).toBeUndefined();
expect(payload.promptMode).toBeUndefined();
});
+
+ it("uses the session id as prompt cache key", async () => {
+ const payload = await capturePayload(getModel("mistral", "mistral-large-latest"), {
+ sessionId: "session-123",
+ });
+
+ expect(payload.promptCacheKey).toBe("session-123");
+ });
+
+ it("omits prompt cache key when cache retention is disabled", async () => {
+ const payload = await capturePayload(getModel("mistral", "mistral-large-latest"), {
+ sessionId: "session-123",
+ cacheRetention: "none",
+ });
+
+ expect(payload.promptCacheKey).toBeUndefined();
+ });
});
diff --git a/packages/ai/test/node-http-proxy.test.ts b/packages/ai/test/node-http-proxy.test.ts
index f4c9b735..a077a928 100644
--- a/packages/ai/test/node-http-proxy.test.ts
+++ b/packages/ai/test/node-http-proxy.test.ts
@@ -54,6 +54,17 @@ describe("node HTTP proxy resolution", () => {
);
});
+ it("prefers scoped proxy env aliases before process env aliases", () => {
+ resetProxyEnv();
+ process.env.https_proxy = "http://process-proxy.example:8080";
+
+ expect(
+ resolveHttpProxyUrlForTarget("https://bedrock-runtime.us-east-1.amazonaws.com", {
+ HTTPS_PROXY: "http://scoped-proxy.example:8080",
+ })?.toString(),
+ ).toBe("http://scoped-proxy.example:8080/");
+ });
+
it("rejects SOCKS and PAC proxy URLs explicitly", () => {
resetProxyEnv();
process.env.HTTPS_PROXY = "socks5://proxy.example:1080";
diff --git a/packages/ai/test/openai-codex-stream.test.ts b/packages/ai/test/openai-codex-stream.test.ts
index f683d678..9c526151 100644
--- a/packages/ai/test/openai-codex-stream.test.ts
+++ b/packages/ai/test/openai-codex-stream.test.ts
@@ -361,13 +361,21 @@ describe("openai-codex streaming", () => {
apiKey: token,
transport: "sse",
}).result();
+ let settled = false;
+ const observedResultPromise = resultPromise.then((result) => {
+ settled = true;
+ return result;
+ });
await vi.advanceTimersByTimeAsync(0);
expect(fetchMock).toHaveBeenCalledTimes(1);
await vi.advanceTimersByTimeAsync(10_000);
- const result = await resultPromise;
+ expect(settled).toBe(false);
+
+ await vi.advanceTimersByTimeAsync(10_000);
+ const result = await observedResultPromise;
expect(result.stopReason).toBe("error");
- expect(result.errorMessage).toBe("Codex SSE response headers timed out after 10000ms");
+ expect(result.errorMessage).toBe("Codex SSE response headers timed out after 20000ms");
});
it("aborts SSE body reads after response headers arrive", async () => {
diff --git a/packages/ai/test/openai-completions-empty-tools.test.ts b/packages/ai/test/openai-completions-empty-tools.test.ts
index 71297a23..5906ea30 100644
--- a/packages/ai/test/openai-completions-empty-tools.test.ts
+++ b/packages/ai/test/openai-completions-empty-tools.test.ts
@@ -162,6 +162,31 @@ describe("openai-completions empty tools handling", () => {
expect(clientOptions.defaultHeaders?.["cf-aig-authorization"]).toBe("Bearer test");
});
+ it("uses provider env before process.env for Cloudflare AI Gateway base URL", async () => {
+ process.env.CLOUDFLARE_ACCOUNT_ID = "process-account";
+ process.env.CLOUDFLARE_GATEWAY_ID = "process-gateway";
+ const model = getModel("cloudflare-ai-gateway", "workers-ai/@cf/moonshotai/kimi-k2.6")!;
+
+ await streamSimple(
+ model,
+ {
+ messages: [{ role: "user", content: "hi", timestamp: Date.now() }],
+ },
+ {
+ apiKey: "test",
+ env: {
+ CLOUDFLARE_ACCOUNT_ID: "provider-account",
+ CLOUDFLARE_GATEWAY_ID: "provider-gateway",
+ },
+ },
+ ).result();
+
+ const clientOptions = mockState.lastClientOptions as { baseURL?: string };
+ expect(clientOptions.baseURL).toBe(
+ "https://gateway.ai.cloudflare.com/v1/provider-account/provider-gateway/compat",
+ );
+ });
+
it("preserves inline upstream Authorization for Cloudflare AI Gateway BYOK requests", async () => {
process.env.CLOUDFLARE_ACCOUNT_ID = "account-id";
process.env.CLOUDFLARE_GATEWAY_ID = "gateway-id";
diff --git a/packages/ai/test/openai-completions-reasoning-details.test.ts b/packages/ai/test/openai-completions-reasoning-details.test.ts
new file mode 100644
index 00000000..88d42874
--- /dev/null
+++ b/packages/ai/test/openai-completions-reasoning-details.test.ts
@@ -0,0 +1,118 @@
+import { Type } from "typebox";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { stream as streamOpenAICompletions } from "../src/api/openai-completions.ts";
+import type { AssistantMessage, Model, Tool } from "../src/types.ts";
+
+const mockState = vi.hoisted(() => ({
+ chunkSets: [] as unknown[][],
+ payloads: [] as unknown[],
+}));
+
+vi.mock("openai", () => {
+ class FakeOpenAI {
+ chat = {
+ completions: {
+ create: (payload: unknown) => {
+ mockState.payloads.push(payload);
+ const chunks = mockState.chunkSets.shift() ?? [];
+ const stream = {
+ async *[Symbol.asyncIterator]() {
+ for (const chunk of chunks) {
+ yield chunk;
+ }
+ },
+ };
+ const result = Promise.resolve(stream) as Promise & {
+ withResponse: () => Promise<{ data: typeof stream; response: { status: number; headers: Headers } }>;
+ };
+ result.withResponse = async () => ({
+ data: stream,
+ response: { status: 200, headers: new Headers() },
+ });
+ return result;
+ },
+ },
+ };
+ }
+ return { default: FakeOpenAI };
+});
+
+const reasoningDetail = { type: "reasoning.encrypted", id: "call_1", data: "encrypted-signature" };
+const readTool: Tool = {
+ name: "read",
+ description: "Read a file",
+ parameters: Type.Object({ path: Type.String() }),
+};
+
+function model(): Model<"openai-completions"> {
+ return {
+ id: "google/gemini-test",
+ name: "Gemini Test",
+ api: "openai-completions",
+ provider: "openrouter",
+ baseUrl: "https://openrouter.ai/api/v1",
+ reasoning: true,
+ input: ["text"],
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
+ contextWindow: 100_000,
+ maxTokens: 4096,
+ };
+}
+
+function chunk(delta: Record, finishReason: string | null = null): unknown {
+ return {
+ id: "chatcmpl-test",
+ model: "google/gemini-test",
+ choices: [{ index: 0, delta, finish_reason: finishReason }],
+ };
+}
+
+function toolCallChunk(): unknown {
+ return chunk({
+ tool_calls: [
+ {
+ index: 0,
+ id: "call_1",
+ type: "function",
+ function: { name: "read", arguments: '{"path":"README.md"}' },
+ },
+ ],
+ });
+}
+
+async function runOpenAICompletionsStream(messages: AssistantMessage[] = []): Promise {
+ return await streamOpenAICompletions(model(), { messages, tools: [readTool] }, { apiKey: "test" }).result();
+}
+
+function getAssistantPayload(payload: unknown): { reasoning_details?: unknown } | undefined {
+ const messages = (payload as { messages?: Array<{ role?: string; reasoning_details?: unknown }> }).messages ?? [];
+ return messages.find((message) => message.role === "assistant");
+}
+
+describe("openai-completions reasoning_details streaming", () => {
+ beforeEach(() => {
+ mockState.chunkSets = [];
+ mockState.payloads = [];
+ });
+
+ it("preserves reasoning_details that arrive before their matching tool call", async () => {
+ mockState.chunkSets = [
+ [chunk({ reasoning_details: [reasoningDetail] }), toolCallChunk(), chunk({}, "tool_calls")],
+ [chunk({ content: "ok" }), chunk({}, "stop")],
+ ];
+
+ const assistantMessage = await runOpenAICompletionsStream();
+ const toolCall = assistantMessage.content.find((block) => block.type === "toolCall");
+ expect(toolCall).toMatchObject({
+ type: "toolCall",
+ id: "call_1",
+ name: "read",
+ arguments: { path: "README.md" },
+ thoughtSignature: JSON.stringify(reasoningDetail),
+ });
+
+ await runOpenAICompletionsStream([assistantMessage]);
+
+ expect(getAssistantPayload(mockState.payloads[1])?.reasoning_details).toEqual([reasoningDetail]);
+ });
+});
diff --git a/packages/ai/test/openai-completions-thinking-as-text.test.ts b/packages/ai/test/openai-completions-thinking-as-text.test.ts
index f8db4788..d1cbe14a 100644
--- a/packages/ai/test/openai-completions-thinking-as-text.test.ts
+++ b/packages/ai/test/openai-completions-thinking-as-text.test.ts
@@ -34,6 +34,7 @@ const compat = {
thinkingFormat: "openai",
openRouterRouting: {},
vercelGatewayRouting: {},
+ chatTemplateKwargs: {},
zaiToolStream: false,
supportsStrictMode: true,
cacheControlFormat: undefined,
diff --git a/packages/ai/test/openai-completions-tool-choice.test.ts b/packages/ai/test/openai-completions-tool-choice.test.ts
index 61083572..927e34b3 100644
--- a/packages/ai/test/openai-completions-tool-choice.test.ts
+++ b/packages/ai/test/openai-completions-tool-choice.test.ts
@@ -2,7 +2,7 @@ import { Type } from "typebox";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { convertMessages } from "../src/api/openai-completions.ts";
import { getModel, stream, streamSimple } from "../src/compat.ts";
-import type { AssistantMessage, Model, Tool, ToolResultMessage } from "../src/types.ts";
+import type { AssistantMessage, Model, SimpleStreamOptions, Tool, ToolResultMessage } from "../src/types.ts";
const mockState = vi.hoisted(() => ({
lastParams: undefined as unknown,
@@ -63,6 +63,46 @@ vi.mock("openai", () => {
return { default: FakeOpenAI };
});
+const localOpenAICompletionsModel = {
+ api: "openai-completions",
+ provider: "local-vllm",
+ baseUrl: "http://localhost:8000/v1",
+ reasoning: true,
+ input: ["text"],
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
+ contextWindow: 128000,
+ maxTokens: 8192,
+} satisfies Omit, "id" | "name" | "compat">;
+
+type CapturedParams = {
+ chat_template_kwargs?: Record;
+ thinking?: unknown;
+ reasoning_effort?: string;
+};
+
+async function captureSimpleParams(
+ model: Model<"openai-completions">,
+ reasoning?: SimpleStreamOptions["reasoning"],
+): Promise {
+ let payload: unknown;
+
+ await streamSimple(
+ model,
+ {
+ messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
+ },
+ {
+ apiKey: "test",
+ reasoning,
+ onPayload: (params: unknown) => {
+ payload = params;
+ },
+ },
+ ).result();
+
+ return (payload ?? mockState.lastParams) as CapturedParams;
+}
+
describe("openai-completions tool_choice", () => {
beforeEach(() => {
mockState.lastParams = undefined;
@@ -256,6 +296,86 @@ describe("openai-completions tool_choice", () => {
expect(getModel("zai", "glm-4.5-air")?.compat?.zaiToolStream).toBeUndefined();
});
+ it("stores z.ai GLM-5.2 effort metadata", () => {
+ for (const provider of ["zai", "zai-coding-cn"] as const) {
+ const model = getModel(provider, "glm-5.2")!;
+ expect(model.compat?.supportsReasoningEffort).toBe(true);
+ expect(model.thinkingLevelMap).toEqual({
+ minimal: null,
+ low: "high",
+ medium: "high",
+ high: "high",
+ xhigh: "max",
+ });
+ }
+ });
+
+ it("maps z.ai GLM-5.2 thinking levels to reasoning_effort", async () => {
+ const model = getModel("zai", "glm-5.2")!;
+ const cases = [
+ { reasoning: "low", effort: "high" },
+ { reasoning: "medium", effort: "high" },
+ { reasoning: "high", effort: "high" },
+ { reasoning: "xhigh", effort: "max" },
+ ] as const;
+
+ for (const testCase of cases) {
+ let payload: unknown;
+
+ await streamSimple(
+ model,
+ {
+ messages: [
+ {
+ role: "user",
+ content: "Hi",
+ timestamp: Date.now(),
+ },
+ ],
+ },
+ {
+ apiKey: "test",
+ reasoning: testCase.reasoning,
+ onPayload: (params: unknown) => {
+ payload = params;
+ },
+ },
+ ).result();
+
+ const params = (payload ?? mockState.lastParams) as { thinking?: unknown; reasoning_effort?: string };
+ expect(params.thinking).toEqual({ type: "enabled" });
+ expect(params.reasoning_effort).toBe(testCase.effort);
+ }
+ });
+
+ it("omits z.ai GLM-5.2 reasoning_effort when thinking is off", async () => {
+ const model = getModel("zai", "glm-5.2")!;
+ let payload: unknown;
+
+ await streamSimple(
+ model,
+ {
+ messages: [
+ {
+ role: "user",
+ content: "Hi",
+ timestamp: Date.now(),
+ },
+ ],
+ },
+ {
+ apiKey: "test",
+ onPayload: (params: unknown) => {
+ payload = params;
+ },
+ },
+ ).result();
+
+ const params = (payload ?? mockState.lastParams) as { thinking?: unknown; reasoning_effort?: string };
+ expect(params.thinking).toEqual({ type: "disabled" });
+ expect(params.reasoning_effort).toBeUndefined();
+ });
+
it("omits tool_stream for unsupported z.ai models", async () => {
const model = getModel("zai", "glm-4.5-air")!;
const tools: Tool[] = [
@@ -1063,6 +1183,7 @@ describe("openai-completions tool_choice", () => {
thinkingFormat: "openai",
openRouterRouting: {},
vercelGatewayRouting: {},
+ chatTemplateKwargs: {},
zaiToolStream: false,
supportsStrictMode: true,
sendSessionAffinityHeaders: false,
@@ -1119,6 +1240,54 @@ describe("openai-completions tool_choice", () => {
expect(params.reasoning_effort).toBeUndefined();
});
+ it("omits disabled thinking for Moonshot Kimi K2.7 Code models", async () => {
+ const cases = [getModel("moonshotai", "kimi-k2.7-code"), getModel("moonshotai-cn", "kimi-k2.7-code")];
+
+ for (const model of cases) {
+ expect(model).toBeDefined();
+ let payload: unknown;
+
+ await streamSimple(
+ model!,
+ {
+ messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
+ },
+ {
+ apiKey: "test",
+ onPayload: (params: unknown) => {
+ payload = params;
+ },
+ },
+ ).result();
+
+ const params = (payload ?? mockState.lastParams) as { thinking?: unknown; reasoning_effort?: string };
+ expect(params.thinking).toBeUndefined();
+ expect(params.reasoning_effort).toBeUndefined();
+ }
+ });
+
+ it("keeps disabled thinking for Moonshot Kimi K2.6 when thinking is off", async () => {
+ const model = getModel("moonshotai-cn", "kimi-k2.6")!;
+ let payload: unknown;
+
+ await streamSimple(
+ model,
+ {
+ messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
+ },
+ {
+ apiKey: "test",
+ onPayload: (params: unknown) => {
+ payload = params;
+ },
+ },
+ ).result();
+
+ const params = (payload ?? mockState.lastParams) as { thinking?: unknown; reasoning_effort?: string };
+ expect(params.thinking).toEqual({ type: "disabled" });
+ expect(params.reasoning_effort).toBeUndefined();
+ });
+
it("sends max_tokens for OpenCode completions models", async () => {
const cases = [getModel("opencode-go", "kimi-k2.6")!, getModel("opencode", "grok-build-0.1")!] as const;
@@ -1322,6 +1491,77 @@ describe("openai-completions tool_choice", () => {
expect(params.reasoning_effort).toBeUndefined();
});
+ it("uses configurable chat template boolean thinking kwargs", async () => {
+ const model = {
+ ...localOpenAICompletionsModel,
+ id: "deepseek-ai/DeepSeek-V3.1",
+ name: "DeepSeek V3.1 via vLLM",
+ compat: {
+ thinkingFormat: "chat-template",
+ supportsReasoningEffort: false,
+ chatTemplateKwargs: { thinking: { $var: "thinking.enabled" } },
+ },
+ } satisfies Model<"openai-completions">;
+
+ for (const testCase of [
+ { reasoning: "high" as const, expected: true },
+ { reasoning: undefined, expected: false },
+ ]) {
+ const params = await captureSimpleParams(model, testCase.reasoning);
+
+ expect(params.chat_template_kwargs).toEqual({ thinking: testCase.expected });
+ expect(params.thinking).toBeUndefined();
+ expect(params.reasoning_effort).toBeUndefined();
+ }
+ });
+
+ it("uses qwen chat template thinking kwargs", async () => {
+ const model = {
+ ...localOpenAICompletionsModel,
+ id: "Qwen/Qwen3-Coder",
+ name: "Qwen3 Coder via vLLM",
+ compat: {
+ thinkingFormat: "qwen-chat-template",
+ supportsReasoningEffort: false,
+ },
+ } satisfies Model<"openai-completions">;
+
+ for (const testCase of [
+ { reasoning: "high" as const, expected: true },
+ { reasoning: undefined, expected: false },
+ ]) {
+ const params = await captureSimpleParams(model, testCase.reasoning);
+
+ expect(params.chat_template_kwargs).toEqual({
+ enable_thinking: testCase.expected,
+ preserve_thinking: true,
+ });
+ expect(params.reasoning_effort).toBeUndefined();
+ }
+ });
+
+ it("uses configurable chat template effort kwargs with static kwargs", async () => {
+ const model = {
+ ...localOpenAICompletionsModel,
+ id: "unsloth/gpt-oss-120b-GGUF",
+ name: "GPT OSS via vLLM",
+ thinkingLevelMap: { xhigh: "max" },
+ compat: {
+ thinkingFormat: "chat-template",
+ supportsReasoningEffort: false,
+ chatTemplateKwargs: {
+ preserve_thinking: true,
+ reasoning_effort: { $var: "thinking.effort", omitWhenOff: true },
+ },
+ },
+ } satisfies Model<"openai-completions">;
+
+ const params = await captureSimpleParams(model, "xhigh");
+
+ expect(params.chat_template_kwargs).toEqual({ preserve_thinking: true, reasoning_effort: "max" });
+ expect(params.reasoning_effort).toBeUndefined();
+ });
+
it("uses Ant Ling compatibility metadata", async () => {
const model = getModel("ant-ling", "Ring-2.6-1T")!;
let payload: unknown;
diff --git a/packages/ai/test/openai-completions-tool-result-images.test.ts b/packages/ai/test/openai-completions-tool-result-images.test.ts
index 758b8767..c8500792 100644
--- a/packages/ai/test/openai-completions-tool-result-images.test.ts
+++ b/packages/ai/test/openai-completions-tool-result-images.test.ts
@@ -32,6 +32,7 @@ const compat: Required = {
thinkingFormat: "openai",
openRouterRouting: {},
vercelGatewayRouting: {},
+ chatTemplateKwargs: {},
zaiToolStream: false,
supportsStrictMode: true,
cacheControlFormat: "anthropic",
diff --git a/packages/ai/test/overflow.test.ts b/packages/ai/test/overflow.test.ts
index 346d2006..62108911 100644
--- a/packages/ai/test/overflow.test.ts
+++ b/packages/ai/test/overflow.test.ts
@@ -49,6 +49,13 @@ describe("isContextOverflow", () => {
expect(isContextOverflow(message, 131072)).toBe(true);
});
+ it("detects OpenAI-compatible parenthesized maximum context length errors", () => {
+ const message = createErrorMessage(
+ "Error: 400 Input length (265330) exceeds model's maximum context length (262144).",
+ );
+ expect(isContextOverflow(message, 262144)).toBe(true);
+ });
+
it("detects OpenRouter Poolside maximum allowed input length errors", () => {
const message = createErrorMessage(
"Provider returned error: Input length 131393 exceeds the maximum allowed input length of 131040 tokens.",
diff --git a/packages/ai/test/supports-xhigh.test.ts b/packages/ai/test/supports-xhigh.test.ts
index ada30669..257758dc 100644
--- a/packages/ai/test/supports-xhigh.test.ts
+++ b/packages/ai/test/supports-xhigh.test.ts
@@ -69,6 +69,15 @@ describe("getSupportedThinkingLevels", () => {
expect(getSupportedThinkingLevels(model!)).toEqual(["off", "high"]);
});
+ it("excludes thinking off for Moonshot Kimi K2.7 Code models", () => {
+ const cases = [getModel("moonshotai", "kimi-k2.7-code"), getModel("moonshotai-cn", "kimi-k2.7-code")];
+
+ for (const model of cases) {
+ expect(model).toBeDefined();
+ expect(getSupportedThinkingLevels(model!)).toEqual(["minimal", "low", "medium", "high"]);
+ }
+ });
+
it("includes only high for OpenCode Grok Build", () => {
const model = getModel("opencode", "grok-build-0.1");
expect(model).toBeDefined();
diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md
index 1f234c1e..aae51db4 100644
--- a/packages/coding-agent/CHANGELOG.md
+++ b/packages/coding-agent/CHANGELOG.md
@@ -10,6 +10,205 @@
- Added an experimental first-time setup flow behind `PI_EXPERIMENTAL=1` that asks for a dark/light theme choice (preselecting the detected appearance) and opt-in analytics data sharing on first launch with the default agent directory; opting in stores a `trackingId` in `settings.json`.
+## [0.79.10] - 2026-06-22
+
+### New Features
+
+- **Extension compaction event context** - Extension `session_before_compact` and `session_compact` events now include `reason` and `willRetry`, so extensions can distinguish manual `/compact`, threshold auto-compaction, and overflow retry flows. See [session_before_compact / session_compact](docs/extensions.md#session_before_compact--session_compact) and [Custom Summarization via Extensions](docs/compaction.md#custom-summarization-via-extensions).
+- **Safer update flow** - `pi update` installs the exact checked Pi version, and update notices show the changelog URL, making upgrades more predictable. See [Install and Manage](docs/packages.md#install-and-manage).
+
+### Added
+
+- Added `reason` and `willRetry` metadata to extension `session_before_compact` and `session_compact` events so extensions can distinguish manual, threshold, and overflow compaction flows ([#5962](https://github.com/earendil-works/pi/pull/5962) by [@PizzaMarinara](https://github.com/PizzaMarinara)).
+
+### Fixed
+
+- Fixed the `find` tool to respect nested git repository boundaries when parent `.gitignore` rules ignore the nested repo ([#5960](https://github.com/earendil-works/pi/issues/5960)).
+- Fixed the usage docs slash command table to include `/trust` and `/import` ([#5959](https://github.com/earendil-works/pi/issues/5959)).
+- Fixed inherited OpenAI-compatible streaming to preserve encrypted `reasoning_details` that arrive before matching tool call deltas ([#5114](https://github.com/earendil-works/pi/issues/5114)).
+- Fixed broken TUI documentation links to the plan-mode extension example ([#5957](https://github.com/earendil-works/pi/issues/5957)).
+- Fixed transient extension UI and session-start messages emitted during session replacement or reload so they remain visible, and kept reload input blocked until reload completes ([#5943](https://github.com/earendil-works/pi/issues/5943)).
+- Fixed the plan-mode example to preserve active custom tools, skip the action prompt when no plan is found, and queue refinement/execution follow-ups correctly from `agent_end` ([#5940](https://github.com/earendil-works/pi/issues/5940)).
+- Fixed `pi update` to install the exact version returned by the Pi update check, make `--force` reinstall that checked version, fail instead of falling back to an unversioned reinstall when no version is available, and report both the old and updated versions.
+- Fixed update notifications to display the actual changelog URL as the hyperlink text.
+
+## [0.79.9] - 2026-06-20
+
+### New Features
+
+- **Chat-template thinking compatibility** - OpenAI-compatible custom providers can map Pi thinking levels into `chat_template_kwargs`, enabling vLLM/Hugging Face chat-template models such as DeepSeek to use provider-native thinking controls. See [Custom Provider API Types](docs/custom-provider.md#api-types) and [OpenAI Compatibility](docs/models.md#openai-compatibility).
+- **GLM-5.2 provider improvements** - GLM-5.2 now has corrected Fireworks OpenAI-compatible routing and OpenRouter `xhigh` thinking support, improving `/model` behavior and high-effort reasoning for GLM-5.2 users. See [Model Options](docs/usage.md#model-options).
+
+### Added
+
+- Added inherited configurable `chat-template` thinking support for OpenAI-compatible providers that use `chat_template_kwargs`, such as DeepSeek models behind vLLM ([#5673](https://github.com/earendil-works/pi/issues/5673)).
+
+### Fixed
+
+- Fixed inherited Fireworks GLM-5.2 metadata to use the OpenAI-compatible Chat Completions endpoint with `reasoning_effort` support ([#5923](https://github.com/earendil-works/pi/issues/5923)).
+- Fixed same-directory session switches to reuse imported extension modules while preserving fresh extension instances and lifecycle events ([#5905](https://github.com/earendil-works/pi/issues/5905)).
+- Fixed deep session branches taking quadratic time to build context or branch paths ([#5909](https://github.com/earendil-works/pi/issues/5909)).
+- Fixed inherited OpenRouter GLM-5.2 metadata to expose `xhigh` reasoning and send OpenRouter's native `xhigh` effort ([#5770](https://github.com/earendil-works/pi/issues/5770)).
+- Fixed inherited Markdown streaming code fence rendering so partial closing fences no longer make code blocks shrink or flicker while content streams ([#5846](https://github.com/earendil-works/pi/pull/5846) by [@xl0](https://github.com/xl0)).
+- Fixed fuzzy `edit` matches to preserve untouched line blocks instead of rewriting the whole file through normalized content ([#5899](https://github.com/earendil-works/pi/issues/5899)).
+- Fixed bash commands through legacy WSL `bash.exe` to pass scripts over stdin so shell variables expand in the target bash ([#5893](https://github.com/earendil-works/pi/issues/5893)).
+- Fixed `/model` to hide GitHub Copilot models that are unavailable to the authenticated account ([#5897](https://github.com/earendil-works/pi/issues/5897)).
+- Fixed `/model` selector search to rank exact provider-prefixed matches before proxy-provider model ID matches ([#5892](https://github.com/earendil-works/pi/issues/5892)).
+
+## [0.79.8] - 2026-06-19
+
+### New Features
+
+- **Selective provider base entry points** - SDK users can pair `@earendil-works/pi-ai/base` and `@earendil-works/pi-agent-core/base` with explicit provider registration to keep bundled applications from including unused provider transports. See [`pi-ai` Base Entry Point](../ai/README.md#base-entry-point) and [`pi-agent-core` Base Entry Point](../agent/README.md#base-entry-point).
+- **Mistral prompt caching** - Mistral sessions now use provider-side prompt caching with session affinity and cached-token usage/cost accounting. See [API Keys](docs/providers.md#api-keys) and [Environment Variables](docs/usage.md#environment-variables).
+- **Post-compaction token estimates** - Compact results and compaction events now include estimated post-compaction token counts so clients can show the approximate context reduction. See [RPC compact](docs/rpc.md#compact) and [compaction events](docs/rpc.md#compaction_start--compaction_end).
+- **OpenRouter Fusion alias** - `openrouter/fusion` is available as a built-in OpenRouter model alias. See [API Keys](docs/providers.md#api-keys).
+
+### Added
+
+- Added inherited `@earendil-works/pi-ai/base` and `@earendil-works/pi-agent-core/base` entry points for selective provider registration in bundled applications ([#5348](https://github.com/earendil-works/pi/pull/5348) by [@FredKSchott](https://github.com/FredKSchott)).
+- Added inherited Mistral prompt caching using the pi session ID as `prompt_cache_key`, including cached-token usage and cost accounting ([#5854](https://github.com/earendil-works/pi/issues/5854)).
+- Added estimated post-compaction token counts to compact results and compaction events ([#5877](https://github.com/earendil-works/pi/issues/5877)).
+- Added the inherited OpenRouter Fusion alias as `openrouter/fusion` ([#5866](https://github.com/earendil-works/pi/pull/5866) by [@dannote](https://github.com/dannote)).
+
+### Fixed
+
+- Updated vulnerable runtime dependencies, including `undici` and the packaged `protobufjs` transitive dependency.
+- Fixed compaction to refuse sessions with no eligible messages instead of producing empty summaries ([#4811](https://github.com/earendil-works/pi/issues/4811)).
+- Fixed successful overflow-triggered auto-compaction to avoid retrying completed assistant responses ([#5720](https://github.com/earendil-works/pi/issues/5720)).
+
+## [0.79.7] - 2026-06-18
+
+### New Features
+
+- **Automatic theme mode** - `/settings` can choose separate light and dark themes and follow terminal color-scheme changes. See [Selecting a Theme](docs/themes.md#selecting-a-theme).
+- **Self-only updates by default** - `pi update` now updates pi only, with `pi update --all` for updating pi and packages together. See [Install and Manage](docs/packages.md#install-and-manage).
+- **Extension API helpers** - extensions can use `CONFIG_DIR_NAME` for project config paths and import edit diff helpers for edit-style diffs. See [`ctx.cwd`](docs/extensions.md#ctxcwd) and [SDK Exports](docs/sdk.md#exports).
+- **Warp inline images** - Warp terminals now get inline image rendering through Kitty graphics detection. See [Image](docs/tui.md#image).
+
+### Added
+
+- Added automatic theme mode so `/settings` can use separate light and dark themes and follow terminal color-scheme changes ([#5874](https://github.com/earendil-works/pi/pull/5874)).
+- Added inherited Warp terminal image capability detection so inline images render through Warp's Kitty graphics support ([#5841](https://github.com/earendil-works/pi/pull/5841) by [@dodiego](https://github.com/dodiego)).
+- Exported `CONFIG_DIR_NAME` from the coding-agent public API so extensions can resolve project config paths without hardcoding `.pi` ([#5869](https://github.com/earendil-works/pi/pull/5869) by [@xl0](https://github.com/xl0)).
+- Exported edit diff helpers (`generateDiffString`, `generateUnifiedPatch`, and `EditDiffResult`) from the public API for extensions that need edit-style diffs ([#5756](https://github.com/earendil-works/pi/pull/5756) by [@xl0](https://github.com/xl0)).
+
+### Changed
+
+- Changed bare `pi update` to update only pi, added `pi update --all` for updating pi and extensions together, and clarified extension update prompts.
+- Reserved `/` in theme names for automatic light/dark theme settings.
+- Updated extension docs, examples, runtime help, trust prompts, and config labels to use the configured project config directory instead of hardcoded `.pi` paths.
+
+### Fixed
+
+- Fixed RPC unknown-command errors to include the request id so clients do not hang waiting for a response ([#5868](https://github.com/earendil-works/pi/issues/5868)).
+- Fixed `/model` autocomplete and model selection searches to match provider/model queries regardless of whether the provider or model token is typed first.
+- Fixed the tree navigator to horizontally pan deep entries so the selected item remains readable ([#5830](https://github.com/earendil-works/pi/issues/5830)).
+
+## [0.79.6] - 2026-06-16
+
+### Fixed
+
+- Fixed HTTP dispatcher configuration to preserve a caller's deliberate `fetch` override instead of reinstalling the undici global fetch over it.
+- Fixed inherited OpenCode Go DeepSeek V4 thinking-off requests to send the provider's `thinking: { type: "disabled" }` compatibility parameter.
+
+## [0.79.5] - 2026-06-16
+
+### New Features
+
+- **Provider-scoped API key environments** - `auth.json` API key entries can now include `env` overrides for provider-specific Cloudflare, Azure OpenAI, Google Vertex, Amazon Bedrock, cache retention, and proxy settings without changing the project shell. See [Auth File](docs/providers.md#auth-file).
+- **Global HTTP proxy setting** - Configure `httpProxy` once in global settings to apply `HTTP_PROXY` and `HTTPS_PROXY` to Pi-managed HTTP clients. See [Network](docs/settings.md#network).
+- **Vercel AI Gateway attribution** - Vercel AI Gateway requests now include Pi attribution headers by default. See [API Keys](docs/providers.md#api-keys).
+
+### Added
+
+- Added Vercel AI Gateway request attribution headers (`http-referer` and `x-title`) for Vercel AI Gateway models ([#5798](https://github.com/earendil-works/pi/pull/5798) by [@rwachtler](https://github.com/rwachtler)).
+- Added an `xp` footer marker when experimental features are enabled.
+- Added a global `httpProxy` setting that applies as `HTTP_PROXY` and `HTTPS_PROXY` for Pi-managed HTTP clients ([#5790](https://github.com/earendil-works/pi/issues/5790)).
+- Added `auth.json` API key `env` values so provider-specific environment overrides can be scoped to Pi and propagated to inherited provider configuration ([#5728](https://github.com/earendil-works/pi/issues/5728)).
+
+### Changed
+
+- Updated the vendored Markdown parser used by HTML session exports to `marked` 18.0.5.
+
+### Fixed
+
+- Fixed inherited OpenAI Responses streaming to tolerate null message content from OpenAI-compatible servers before tool calls ([#5819](https://github.com/earendil-works/pi/issues/5819)).
+- Fixed inherited OpenCode DeepSeek V4 thinking requests to avoid sending both `thinking` and `reasoning_effort` ([#5818](https://github.com/earendil-works/pi/issues/5818)).
+- Fixed device-code login to stop opening the browser automatically.
+- Fixed inherited editor Cursor Up handling so non-empty drafts jump to the start of the line before browsing input history ([#5789](https://github.com/earendil-works/pi/pull/5789) by [@4h9fbZ](https://github.com/4h9fbZ)).
+- Fixed inherited Z.AI GLM-5.2 thinking requests to send `reasoning_effort` with the provider's `high`/`max` effort mapping ([#5770](https://github.com/earendil-works/pi/issues/5770)).
+- Fixed successful `pi update` on Windows to exit naturally instead of calling `process.exit(0)`, avoiding a Node.js/libuv assertion after version-check network requests ([#5805](https://github.com/earendil-works/pi/issues/5805)).
+- Fixed inherited Google and `google-vertex` Gemini model metadata to map `latest` aliases to the current models, add Gemini 3.5 Flash for Vertex, correct Gemini 2.5 Flash Vertex cache pricing, and remove shut-down Vertex preview models ([#5761](https://github.com/earendil-works/pi/issues/5761)).
+- Fixed the session selector to stay open and show the all-sessions empty state when both current-folder and all-scope session lists are empty ([#5747](https://github.com/earendil-works/pi/issues/5747)).
+- Fixed inherited Moonshot AI China model metadata to include Kimi K2.7 Code, and omitted unsupported thinking-off payloads for Kimi K2.7 Code models ([#5760](https://github.com/earendil-works/pi/issues/5760)).
+
+## [0.79.4] - 2026-06-15
+
+### New Features
+
+- **Automatic first-run theme selection** - pi detects the terminal background on first run and defaults to the `dark` or `light` theme. See [Selecting a Theme](docs/themes.md#selecting-a-theme).
+- **Standalone binary integrity checksums** - GitHub release assets now include `SHA256SUMS` files for verifying standalone binary downloads. See [Quickstart Install](docs/quickstart.md#install).
+
+### Added
+
+- Added `SHA256SUMS` integrity files to standalone binary GitHub release assets ([#5739](https://github.com/earendil-works/pi/issues/5739)).
+- Added first-run interactive theme detection from the terminal background ([#5385](https://github.com/earendil-works/pi/pull/5385) by [@vegarsti](https://github.com/vegarsti)).
+
+### Fixed
+
+- Fixed bash tool output collection to keep draining stdout/stderr after the child exits while descendants still write, avoiding truncated late output ([#5753](https://github.com/earendil-works/pi/pull/5753) by [@Mearman](https://github.com/Mearman)).
+- Fixed `/tree` help rendering to show compact wrapped controls instead of truncating them on narrow terminals ([#5055](https://github.com/earendil-works/pi/issues/5055)).
+- Fixed SIGTERM/SIGHUP interactive shutdown to keep signal handlers installed until terminal cleanup completes, preventing `signal-exit` from re-sending the signal and leaving the terminal in raw/Kitty keyboard mode ([#5724](https://github.com/earendil-works/pi/issues/5724)).
+- Fixed extensions documentation to clarify that `pi.getActiveTools()` returns active tool names while `pi.getAllTools()` returns tool metadata ([#5729](https://github.com/earendil-works/pi/issues/5729)).
+- Fixed question and questionnaire extension examples to wrap long prompt, option, and help text instead of truncating it ([#5708](https://github.com/earendil-works/pi/pull/5708) by [@xl0](https://github.com/xl0)).
+- Fixed package commands such as `pi list`, `pi install`, and `pi update` to terminate after completing even if an extension leaves background handles open ([#5687](https://github.com/earendil-works/pi/issues/5687)).
+- Fixed `pi update` for pnpm global installs whose configured `global-bin-dir` no longer matches the active pnpm home ([#5689](https://github.com/earendil-works/pi/issues/5689)).
+- Fixed npm package specs that use ranges or tags (for example `@^1.2.7`) so installed package resources still load instead of being treated as mismatched exact pins ([#5695](https://github.com/earendil-works/pi/issues/5695)).
+- Fixed inherited Anthropic 1-hour prompt-cache write cost accounting to price 1-hour cache writes at 2x input instead of the 5-minute cache-write rate ([#5738](https://github.com/earendil-works/pi/pull/5738) by [@theBucky](https://github.com/theBucky)).
+- Fixed inherited GitHub Copilot Claude adaptive-thinking effort metadata to match manually checked Copilot model capabilities ([#4637](https://github.com/earendil-works/pi/issues/4637)).
+- Fixed inherited OpenCode/OpenCode Go completion model metadata to omit long-retention cache fields for routes that reject `prompt_cache_retention` ([#5702](https://github.com/earendil-works/pi/issues/5702)).
+- Fixed inherited overlay compositing over CJK wide characters so borders stay aligned when an overlay starts inside a full-width cell ([#5297](https://github.com/earendil-works/pi/issues/5297)).
+- Fixed inherited WezTerm inline Kitty image rendering during full redraw fallbacks so image padding rows are reserved before the placement is drawn without regressing tall-image placement ([#5618](https://github.com/earendil-works/pi/issues/5618), [#4415](https://github.com/earendil-works/pi/issues/4415)).
+- Fixed custom provider config so plain uppercase API key and header values remain literals instead of being treated as legacy environment references; use explicit `$ENV_VAR` syntax for environment variables ([#5661](https://github.com/earendil-works/pi/issues/5661)).
+
+## [0.79.3] - 2026-06-13
+
+### Fixed
+
+- Fixed inherited OpenAI GPT-5.4/GPT-5.5 and OpenAI Codex GPT-5.4/GPT-5.4 mini/GPT-5.5 context window metadata to use the observed 272k-token Codex backend limit, avoiding a billing hazard from prompts above Codex's accepted limit (reported by [@trethore](https://github.com/trethore)).
+
+## [0.79.2] - 2026-06-12
+
+### New Features
+
+- **Clearer Bedrock validation guidance** - Amazon Bedrock data retention validation errors now link to AWS data retention documentation. See [Amazon Bedrock](docs/providers.md#amazon-bedrock).
+
+### Added
+
+- Added an experimental first-time setup flow behind `PI_EXPERIMENTAL=1` that asks for a dark/light theme choice (preselecting the detected appearance) and opt-in analytics data sharing on first launch with the default agent directory; opting in stores a `trackingId` in `settings.json` ([#5587](https://github.com/earendil-works/pi/pull/5587) by [@vegarsti](https://github.com/vegarsti)).
+- Added AWS data retention documentation links to inherited Amazon Bedrock unsupported data retention mode validation errors ([#5561](https://github.com/earendil-works/pi/pull/5561) by [@unexge](https://github.com/unexge)).
+
+### Fixed
+
+- Fixed project trust detection to ignore global `~/.pi/agent` state when running from `$HOME`, and made `pi update` use only saved or explicit project trust without prompting ([#5619](https://github.com/earendil-works/pi/issues/5619)).
+- Fixed experimental first-time setup to skip forked sessions instead of rerunning the setup prompts ([#5627](https://github.com/earendil-works/pi/pull/5627) by [@vegarsti](https://github.com/vegarsti)).
+- Fixed inherited OpenAI-compatible context overflow detection for parenthesized `maximum context length (N)` errors ([#5677](https://github.com/earendil-works/pi/issues/5677)).
+- Fixed inherited OpenAI GPT-5.4/GPT-5.5 and OpenAI Codex GPT-5.4/GPT-5.4 mini/GPT-5.5 context window metadata to match current OpenAI limits ([#5644](https://github.com/earendil-works/pi/issues/5644)).
+- Fixed inherited Anthropic refusal stops to preserve provider `stop_details` explanations in error messages ([#5666](https://github.com/earendil-works/pi/pull/5666) by [@rwachtler](https://github.com/rwachtler)).
+- Increased the inherited OpenAI Codex Responses SSE response-header timeout to 20 seconds to reduce false-positive stalls while retaining the bounded wait introduced for zero-event hangs ([#4945](https://github.com/earendil-works/pi/issues/4945)).
+- Fixed inherited Claude Fable 5 thinking-off requests to omit Anthropic's unsupported `thinking.type: "disabled"` payload ([#5567](https://github.com/earendil-works/pi/pull/5567) by [@tmustier](https://github.com/tmustier)).
+- Fixed inherited late tool progress callbacks after tool settlement to be ignored instead of emitting stale `tool_execution_update` events ([#5573](https://github.com/earendil-works/pi/issues/5573)).
+- Fixed inherited user-message transcript rendering so standalone `+` messages no longer render as `-` ([#5657](https://github.com/earendil-works/pi/issues/5657)).
+- Fixed inherited slash-separated fuzzy queries so provider/model completions remain matchable after insertion.
+- Fixed inherited WezTerm inline Kitty image rendering so reserved row clears do not erase all but the top strip of tool image previews ([#5618](https://github.com/earendil-works/pi/issues/5618)).
+- Fixed inherited editor wrapping for CJK text to break at character boundaries instead of leaving large trailing gaps ([#5585](https://github.com/earendil-works/pi/pull/5585) by [@haoqixu](https://github.com/haoqixu)).
+- Fixed inherited loose Markdown list rendering to preserve blank-line separation between list items ([#5562](https://github.com/earendil-works/pi/pull/5562) by [@Perlence](https://github.com/Perlence)).
+- Fixed `--model` resolution for authenticated custom model IDs whose slash prefix matches an unauthenticated built-in provider ([#5643](https://github.com/earendil-works/pi/issues/5643)).
+- Fixed `/fork` to keep session parent chains connected when the forked path contains labels ([#5669](https://github.com/earendil-works/pi/issues/5669)).
+- Fixed `/share` and `/export` HTML exports to use the active fallback theme when the configured custom theme no longer exists ([#5596](https://github.com/earendil-works/pi/issues/5596)).
+- Fixed custom fallback model IDs with `:` suffixes to preserve the requested thinking level when the provider template model does not advertise reasoning ([#5560](https://github.com/earendil-works/pi/pull/5560) by [@haoqixu](https://github.com/haoqixu)).
+
## [0.79.1] - 2026-06-09
### New Features
diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md
index 6e5c7059..5dc991cc 100644
--- a/packages/coding-agent/README.md
+++ b/packages/coding-agent/README.md
@@ -7,11 +7,6 @@
-
- pi.dev domain graciously donated by
-
- exe.dev
-
> New issues and PRs from new contributors are auto-closed by default. Maintainers review auto-closed issues daily. See [CONTRIBUTING.md](../../CONTRIBUTING.md).
@@ -191,7 +186,8 @@ Type `/` in the editor to trigger commands. [Extensions](#extensions) can regist
| `/clone` | Duplicate the current active branch into a new session |
| `/compact [prompt]` | Manually compact context, optional custom instructions |
| `/copy` | Copy last assistant message to clipboard |
-| `/export [file]` | Export session to HTML file |
+| `/export [file]` | Export session to HTML or JSONL file |
+| `/import ` | Import and resume a session from a JSONL file |
| `/share` | Upload as private GitHub gist with shareable HTML link |
| `/reload` | Reload keybindings, extensions, skills, prompts, and context files (themes hot-reload automatically) |
| `/hotkeys` | Show all keyboard shortcuts |
@@ -291,15 +287,15 @@ See [docs/settings.md](docs/settings.md) for all options.
### Project Trust
-On interactive startup, pi asks before trusting a project folder that contains project-local extensions or settings and has no saved decision for the folder or a parent folder in `~/.pi/agent/trust.json`. Trusting a project allows pi to load `.pi/settings.json` and `.pi` resources, install missing project packages, and execute project extensions.
+On interactive startup, pi asks before trusting a project folder that contains project-local settings, resources, or project `.agents/skills` and has no saved decision for the folder or a parent folder in `~/.pi/agent/trust.json`. Trusting a project allows pi to load `.pi/settings.json` and `.pi` resources, install missing project packages, and execute project extensions.
Before the trust decision, pi loads only context files, user/global extensions, and CLI `-e` extensions so they can handle the `project_trust` event. Project-local extensions, project package-managed extensions, and project settings are loaded only after the project is trusted. This split also applies when switching to a session from a different cwd whose trust has not been resolved in the current process.
-Non-interactive modes (`-p`, `--mode json`, and `--mode rpc`) do not show a trust prompt. Without an applicable saved trust decision, they use `defaultProjectTrust` from global settings: `ask` (default) and `never` ignore trust-gated project inputs, while `always` trusts them. Pass `--approve`/`-a` or `--no-approve`/`-na` to override project trust for one run.
+Non-interactive modes (`-p`, `--mode json`, and `--mode rpc`) do not show a trust prompt. Without an applicable saved trust decision, they use `defaultProjectTrust` from global settings: `ask` (default) and `never` ignore those project resources, while `always` trusts them. Pass `--approve`/`-a` or `--no-approve`/`-na` to override project trust for one run.
If no extension or saved decision applies, `defaultProjectTrust` controls the fallback behavior. Set it to `"ask"`, `"always"`, or `"never"` in `~/.pi/agent/settings.json`, or change it with `/settings`.
-`pi config` and package commands use the same project trust flow. Pass `--approve` to trust project-local settings for one command or `--no-approve` to ignore them.
+`pi config` and package commands use the same project trust flow, except `pi update` never prompts. Pass `--approve` to trust project-local settings for one command or `--no-approve` to ignore them.
Use `/trust` in interactive mode to save a project trust decision for future sessions, including trust for the immediate parent folder. It writes `~/.pi/agent/trust.json` only; the current session is not reloaded, so restart pi for changes to take effect.
@@ -419,7 +415,8 @@ pi install ssh://git@github.com/user/repo@v1 # tag or commit
pi remove npm:@foo/pi-tools
pi uninstall npm:@foo/pi-tools # alias for remove
pi list
-pi update # update pi and packages (skips pinned packages)
+pi update # update pi only
+pi update --all # update pi and packages
pi update --extensions # update packages only
pi update --self # update pi only
pi update --self --force # reinstall pi even if current
@@ -427,7 +424,7 @@ pi update npm:@foo/pi-tools # update one package
pi config # enable/disable extensions, skills, prompts, themes
```
-Packages install to `~/.pi/agent/git/` (git) or `~/.pi/agent/npm/` (npm). Use `-l` for project-local installs (`.pi/git/`, `.pi/npm/`). Git `@ref` values are pinned tags or commits; pinned packages are skipped by `pi update`, so use `pi install git:host/user/repo@new-ref` to move an existing package to a new ref. Git packages install dependencies with `npm install --omit=dev` by default, so runtime deps must be listed under `dependencies`; when `npmCommand` is configured, git packages use plain `install` for compatibility with wrappers. If you use a Node version manager and want package installs to reuse a stable npm context, set `npmCommand` in `settings.json`, for example `["mise", "exec", "node@20", "--", "npm"]`.
+Packages install to `~/.pi/agent/git/` (git) or `~/.pi/agent/npm/` (npm). Use `-l` for project-local installs (`.pi/git/`, `.pi/npm/`). Git `@ref` values are pinned tags or commits; pinned packages are skipped by `pi update --extensions` and `pi update --all`, so use `pi install git:host/user/repo@new-ref` to move an existing package to a new ref. Git packages install dependencies with `npm install --omit=dev` by default, so runtime deps must be listed under `dependencies`; when `npmCommand` is configured, git packages use plain `install` for compatibility with wrappers. If you use a Node version manager and want package installs to reuse a stable npm context, set `npmCommand` in `settings.json`, for example `["mise", "exec", "node@20", "--", "npm"]`.
Create a package by adding a `pi` key to `package.json`:
@@ -518,7 +515,8 @@ pi [options] [@files...] [messages...]
pi install [-l] # Install package, -l for project-local
pi remove [-l] # Remove package
pi uninstall [-l] # Alias for remove
-pi update [source|self|pi] # Update pi and packages (skips pinned packages)
+pi update [source|self|pi] # Update pi only, or one package source
+pi update --all # Update pi and packages
pi update --extensions # Update packages only
pi update --self # Update pi only
pi update --self --force # Reinstall pi even if current
@@ -527,7 +525,7 @@ pi list # List installed packages
pi config # Enable/disable package resources
```
-`pi config` and project package commands accept `--approve`/`--no-approve` to trust or ignore project-local settings for one command.
+`pi config` and project package commands accept `--approve`/`--no-approve` to trust or ignore project-local settings for one command. `pi update` never prompts for project trust.
### Modes
@@ -673,8 +671,6 @@ pi --thinking high "Solve this complex problem"
See [CONTRIBUTING.md](../../CONTRIBUTING.md) for guidelines and [docs/development.md](docs/development.md) for setup, forking, and debugging.
----
-
## License
MIT
@@ -684,3 +680,9 @@ MIT
- [@earendil-works/pi-ai](https://www.npmjs.com/package/@earendil-works/pi-ai): Core LLM toolkit
- [@earendil-works/pi-agent-core](https://www.npmjs.com/package/@earendil-works/pi-agent-core): Agent framework
- [@earendil-works/pi-tui](https://www.npmjs.com/package/@earendil-works/pi-tui): Terminal UI components
+
+
+ pi.dev domain graciously donated by
+
+ exe.dev
+
diff --git a/packages/coding-agent/docs/compaction.md b/packages/coding-agent/docs/compaction.md
index babcddc6..5e0d4eef 100644
--- a/packages/coding-agent/docs/compaction.md
+++ b/packages/coding-agent/docs/compaction.md
@@ -276,7 +276,7 @@ Fired before auto-compaction or `/compact`. Can cancel or provide custom summary
```typescript
pi.on("session_before_compact", async (event, ctx) => {
- const { preparation, branchEntries, customInstructions, signal } = event;
+ const { preparation, branchEntries, customInstructions, reason, willRetry, signal } = event;
// preparation.messagesToSummarize - messages to summarize
// preparation.turnPrefixMessages - split turn prefix (if isSplitTurn)
@@ -287,6 +287,8 @@ pi.on("session_before_compact", async (event, ctx) => {
// preparation.settings - compaction settings
// branchEntries - all entries on current branch (for custom state)
+ // reason - "manual" (/compact), "threshold", or "overflow"
+ // willRetry - whether the aborted turn is retried after compaction (overflow recovery)
// signal - AbortSignal (pass to LLM calls)
// Cancel:
diff --git a/packages/coding-agent/docs/containerization.md b/packages/coding-agent/docs/containerization.md
index a3a96bee..33f2df3d 100644
--- a/packages/coding-agent/docs/containerization.md
+++ b/packages/coding-agent/docs/containerization.md
@@ -10,46 +10,12 @@ There are two general options. You can either
| Pattern | What is isolated | Best for | Notes |
| --- | --- | --- | --- |
-| OpenShell | Whole `pi` process in a policy-controlled sandbox | Local or remote managed sandbox | Requires an OpenShell gateway |
| Gondolin extension | Built-in tools and `!` commands | Local micro-VM isolation while keeping auth on host | See [`examples/extensions/gondolin/`](../examples/extensions/gondolin/). |
| Plain Docker | Whole `pi` process in a local container | Simple local isolation | Provider API keys enter the container. |
+| OpenShell | Whole `pi` process in a policy-controlled sandbox | Local or remote managed sandbox | Requires an OpenShell gateway |
Extensions run wherever the `pi` process runs. If you run host `pi` with a tool-routing extension, other custom extension tools still run on the host unless they also delegate their operations.
-## OpenShell
-
-Use [NVIDIA OpenShell](https://docs.nvidia.com/openshell/about/overview) when you want a policy-controlled sandbox with filesystem, process, network, credential, and inference controls.
-OpenShell can run sandboxes through a local gateway backed by Docker, Podman, or a VM runtime, or through a remote Kubernetes gateway.
-
-Every sandbox requires an active gateway.
-Register and select one before creating a sandbox:
-
-```bash
-openshell gateway add --name
-openshell gateway select
-```
-
-Launch `pi` inside an OpenShell sandbox:
-
-```bash
-openshell sandbox create --name pi-sandbox --from pi -- pi
-```
-
-In this pattern, the whole `pi` process runs inside the sandbox.
-Built-in tools, `!` commands, and extension tools execute inside the OpenShell boundary.
-
-If the gateway is remote, project files are not bind-mounted from the host, meaning writes in the sandbox are not reflected on your machine.
-Clone the repository inside the sandbox or use OpenShell file transfer commands:
-
-```bash
-openshell sandbox upload pi-sandbox ./repo /workspace
-openshell sandbox download pi-sandbox /workspace/repo ./repo-out
-```
-
-OpenShell providers can keep raw model API keys outside the sandbox.
-When inference routing is configured, code inside the sandbox can call `https://inference.local`, and the gateway injects the configured provider credentials upstream.
-Configure Pi to use the corresponding OpenAI-compatible or Anthropic-compatible endpoint if you want model traffic to use this route.
-
## Gondolin
[Gondolin](https://github.com/earendil-works/gondolin) is a local Linux micro-VM.
@@ -109,3 +75,37 @@ docker run --rm -it \
The `-v "$PWD:/workspace"` mounts your current directory into the container at /workspace such that reads and writes in `/workspace` inside Docker directly affect your host files, like in the Gondolin example.
Use a named volume for `/root/.pi/agent` if you want container-local settings and sessions. Mounting your host `~/.pi/agent` exposes host auth and session files to the container.
+
+## OpenShell
+
+Use [NVIDIA OpenShell](https://docs.nvidia.com/openshell/about/overview) when you want a policy-controlled sandbox with filesystem, process, network, credential, and inference controls.
+OpenShell can run sandboxes through a local gateway backed by Docker, Podman, or a VM runtime, or through a remote Kubernetes gateway.
+
+Every sandbox requires an active gateway.
+Register and select one before creating a sandbox:
+
+```bash
+openshell gateway add --name
+openshell gateway select
+```
+
+Launch `pi` inside an OpenShell sandbox:
+
+```bash
+openshell sandbox create --name pi-sandbox --from pi -- pi
+```
+
+In this pattern, the whole `pi` process runs inside the sandbox.
+Built-in tools, `!` commands, and extension tools execute inside the OpenShell boundary.
+
+If the gateway is remote, project files are not bind-mounted from the host, meaning writes in the sandbox are not reflected on your machine.
+Clone the repository inside the sandbox or use OpenShell file transfer commands:
+
+```bash
+openshell sandbox upload pi-sandbox ./repo /workspace
+openshell sandbox download pi-sandbox /workspace/repo ./repo-out
+```
+
+OpenShell providers can keep raw model API keys outside the sandbox.
+When inference routing is configured, code inside the sandbox can call `https://inference.local`, and the gateway injects the configured provider credentials upstream.
+Configure Pi to use the corresponding OpenAI-compatible or Anthropic-compatible endpoint if you want model traffic to use this route.
diff --git a/packages/coding-agent/docs/custom-provider.md b/packages/coding-agent/docs/custom-provider.md
index c2bf4455..612d1e60 100644
--- a/packages/coding-agent/docs/custom-provider.md
+++ b/packages/coding-agent/docs/custom-provider.md
@@ -229,7 +229,7 @@ models: [{
}]
```
-Use `openrouter` for OpenRouter-style `reasoning: { effort }` controls. Use `together` for Together-style `reasoning: { enabled }` controls; with `supportsReasoningEffort`, it also sends `reasoning_effort`. Use `qwen-chat-template` instead for local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking`.
+Use `openrouter` for OpenRouter-style `reasoning: { effort }` controls. Use `together` for Together-style `reasoning: { enabled }` controls; with `supportsReasoningEffort`, it also sends `reasoning_effort`. Use `qwen-chat-template` for local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking` and need `preserve_thinking`.
Use `cacheControlFormat: "anthropic"` for OpenAI-compatible providers that expose Anthropic-style prompt caching via `cache_control` on the system prompt, last tool definition, and last user/assistant text content.
For Anthropic-compatible providers using `api: "anthropic-messages"`, set `compat.forceAdaptiveThinking: true` on models or providers whose upstream model requires adaptive thinking (`thinking.type: "adaptive"` plus `output_config.effort`). Built-in adaptive Claude models set this automatically. Set `compat.allowEmptySignature: true` only for providers that emit empty thinking signatures and expect `signature: ""` on replay.
@@ -718,7 +718,8 @@ interface ProviderModelConfig {
requiresAssistantAfterToolResult?: boolean;
requiresThinkingAsText?: boolean;
requiresReasoningContentOnAssistantMessages?: boolean;
- thinkingFormat?: "openai" | "openrouter" | "deepseek" | "together" | "zai" | "qwen" | "qwen-chat-template";
+ thinkingFormat?: "openai" | "openrouter" | "deepseek" | "together" | "zai" | "qwen" | "chat-template" | "qwen-chat-template" | "string-thinking" | "ant-ling";
+ chatTemplateKwargs?: Record;
cacheControlFormat?: "anthropic";
// anthropic-messages
@@ -732,5 +733,5 @@ interface ProviderModelConfig {
}
```
-`openrouter` sends `reasoning: { effort }`. `deepseek` sends `thinking: { type: "enabled" | "disabled" }` and `reasoning_effort` when enabled. `together` sends `reasoning: { enabled }` and also `reasoning_effort` when `supportsReasoningEffort` is enabled. `qwen` is for DashScope-style top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking`.
+`openrouter` sends `reasoning: { effort }`. `deepseek` sends `thinking: { type: "enabled" | "disabled" }` and `reasoning_effort` when enabled. `together` sends `reasoning: { enabled }` and also `reasoning_effort` when `supportsReasoningEffort` is enabled. `qwen` is for DashScope-style top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking` and need `preserve_thinking`. Use `chat-template` for configurable `chat_template_kwargs`, for example DeepSeek V3.x behind vLLM with `chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } }`.
`cacheControlFormat: "anthropic"` applies Anthropic-style `cache_control` markers to the system prompt, last tool definition, and last user/assistant text content.
diff --git a/packages/coding-agent/docs/extensions.md b/packages/coding-agent/docs/extensions.md
index cb02960f..a9271ee8 100644
--- a/packages/coding-agent/docs/extensions.md
+++ b/packages/coding-agent/docs/extensions.md
@@ -216,6 +216,12 @@ export default async function (pi: ExtensionAPI) {
This pattern makes the fetched models available during normal startup and to `pi --list-models`.
+### Long-lived resources and shutdown
+
+Extension factories may run in invocations that never start a session. Do not start background resources such as processes, sockets, file watchers, or timers from the factory.
+
+Defer background resource startup until `session_start` or the command/tool/event that needs the resource. Register an idempotent `session_shutdown` handler to close any session-scoped resources you start.
+
### Extension Styles
**Single file** - simplest, for small extensions:
@@ -431,7 +437,10 @@ Fired on compaction. See [compaction.md](compaction.md) for details.
```typescript
pi.on("session_before_compact", async (event, ctx) => {
- const { preparation, branchEntries, customInstructions, signal } = event;
+ const { preparation, branchEntries, customInstructions, reason, willRetry, signal } = event;
+
+ // reason - "manual" (/compact), "threshold", or "overflow"
+ // willRetry - whether the aborted turn is retried after compaction (overflow recovery)
// Cancel:
return { cancel: true };
@@ -449,6 +458,8 @@ pi.on("session_before_compact", async (event, ctx) => {
pi.on("session_compact", async (event, ctx) => {
// event.compactionEntry - the saved compaction
// event.fromExtension - whether extension provided it
+ // event.reason - "manual" (/compact), "threshold", or "overflow"
+ // event.willRetry - whether the aborted turn is retried after compaction (overflow recovery)
});
```
@@ -471,7 +482,7 @@ pi.on("session_tree", async (event, ctx) => {
#### session_shutdown
-Fired before an extension runtime is torn down.
+Fired before a started session runtime is torn down. Use this to clean up resources opened from `session_start` or other session-scoped hooks.
```typescript
pi.on("session_shutdown", async (event, ctx) => {
@@ -892,6 +903,20 @@ Current run mode: `"tui"`, `"rpc"`, `"json"`, or `"print"`. Use `ctx.mode === "t
Current working directory.
+Use `CONFIG_DIR_NAME` instead of hardcoding `.pi` when constructing project-local config paths. Rebranded distributions can use a different config directory name.
+
+```typescript
+import { CONFIG_DIR_NAME, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
+import { join } from "node:path";
+
+export default function (pi: ExtensionAPI) {
+ pi.on("session_start", (_event, ctx) => {
+ const projectConfigPath = join(ctx.cwd, CONFIG_DIR_NAME, "my-extension.json");
+ // ...
+ });
+}
+```
+
### ctx.isProjectTrusted()
Returns whether project-local trust is active for the current session context. This includes temporary trust decisions and CLI trust overrides, not just saved decisions in the global trust store.
@@ -1528,21 +1553,21 @@ const result = await pi.exec("git", ["status"], { signal, timeout: 5000 });
### pi.getActiveTools() / pi.getAllTools() / pi.setActiveTools(names)
-Manage active tools. This works for both built-in tools and dynamically registered tools.
+Manage active tools. This works for both built-in tools and dynamically registered tools. `pi.getActiveTools()` returns the active tool names as `string[]`; `pi.getAllTools()` returns metadata for all configured tools.
```typescript
-const active = pi.getActiveTools();
+const active = pi.getActiveTools(); // ["read", "bash", ...]
const all = pi.getAllTools();
-// [{
+// all = [{
// name: "read",
// description: "Read file contents...",
// parameters: ...,
// promptGuidelines: ["Use read to examine files instead of cat or sed."],
// sourceInfo: { path: "", source: "builtin", scope: "temporary", origin: "top-level" }
// }, ...]
-const names = all.map(t => t.name);
const builtinTools = all.filter((t) => t.sourceInfo.source === "builtin");
const extensionTools = all.filter((t) => t.sourceInfo.source !== "builtin" && t.sourceInfo.source !== "sdk");
+pi.setActiveTools([...new Set([...active, "my_custom_tool"])]); // Keep current tools and enable my_custom_tool
pi.setActiveTools(["read", "bash"]); // Switch to read-only
```
diff --git a/packages/coding-agent/docs/index.md b/packages/coding-agent/docs/index.md
index 71995831..b5ee017e 100644
--- a/packages/coding-agent/docs/index.md
+++ b/packages/coding-agent/docs/index.md
@@ -42,7 +42,7 @@ For the full first-run flow, see [Quickstart](quickstart.md).
- [Using Pi](usage.md) - interactive mode, slash commands, context files, and CLI reference.
- [Providers](providers.md) - subscription and API-key setup for built-in providers.
- [Security](security.md) - project trust, sandbox boundaries, and vulnerability reporting.
-- [Containerization](containerization.md) - sandbox pi with OpenShell, Gondolin, or Docker.
+- [Containerization](containerization.md) - sandbox pi with Gondolin, Docker, or OpenShell.
- [Settings](settings.md) - global and project settings.
- [Keybindings](keybindings.md) - default shortcuts and custom keybindings.
- [Sessions](sessions.md) - session management, branching, and tree navigation.
diff --git a/packages/coding-agent/docs/models.md b/packages/coding-agent/docs/models.md
index 39b56aa1..5679981a 100644
--- a/packages/coding-agent/docs/models.md
+++ b/packages/coding-agent/docs/models.md
@@ -161,13 +161,11 @@ The `apiKey` and `headers` fields support command execution, environment interpo
"apiKey": "$$literal-dollar-prefix"
"apiKey": "$!literal-bang-prefix"
```
-- **Literal value:** Used directly
+- **Literal value:** Used directly. Plain uppercase strings such as `MY_API_KEY` are literals; use `$MY_API_KEY` for environment variables.
```json
"apiKey": "sk-..."
```
-Legacy uppercase env-var-like values such as `MY_API_KEY` are migrated to `$MY_API_KEY` on startup.
-
For `models.json`, shell commands are resolved at request time. pi intentionally does not apply built-in TTL, stale reuse, or recovery logic for arbitrary commands. Different commands need different caching and failure strategies, and pi cannot infer the right one.
If your command is slow, expensive, rate-limited, or should keep using a previous value on transient failures, wrap it in your own script or command that implements the caching or TTL behavior you want.
@@ -401,14 +399,15 @@ For providers with partial OpenAI compatibility, use the `compat` field.
| `requiresAssistantAfterToolResult` | Insert an assistant message before a user message after tool results |
| `requiresThinkingAsText` | Convert thinking blocks to plain text |
| `requiresReasoningContentOnAssistantMessages` | Include empty `reasoning_content` on all replayed assistant messages when reasoning is enabled |
-| `thinkingFormat` | Use `reasoning_effort`, `openrouter`, `deepseek`, `together`, `zai`, `qwen`, or `qwen-chat-template` thinking parameters |
+| `thinkingFormat` | Use `reasoning_effort`, `openrouter`, `deepseek`, `together`, `zai`, `qwen`, `chat-template`, or `qwen-chat-template` thinking parameters |
+| `chatTemplateKwargs` | `chat_template_kwargs` values for `thinkingFormat: "chat-template"`; use `{ "$var": "thinking.enabled" }` or `{ "$var": "thinking.effort" }` for pi-controlled thinking values |
| `cacheControlFormat` | Use Anthropic-style `cache_control` markers on the system prompt, last tool definition, and last user/assistant text content. Currently only `anthropic` is supported. |
| `supportsStrictMode` | Include the `strict` field in tool definitions |
| `supportsLongCacheRetention` | Whether the provider accepts long cache retention when cache retention is `long`: `prompt_cache_retention: "24h"` for OpenAI prompt caching, or `cache_control.ttl: "1h"` when `cacheControlFormat` is `anthropic`. Default: `true`. |
| `openRouterRouting` | OpenRouter provider routing preferences. This object is sent as-is in the `provider` field of the [OpenRouter API request](https://openrouter.ai/docs/guides/routing/provider-selection). |
| `vercelGatewayRouting` | Vercel AI Gateway routing config for provider selection (`only`, `order`) |
-`openrouter` uses `reasoning: { effort }`. `together` uses `reasoning: { enabled }` and also `reasoning_effort` when `supportsReasoningEffort` is enabled. `qwen` uses top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that require `chat_template_kwargs.enable_thinking`.
+`openrouter` uses `reasoning: { effort }`. `together` uses `reasoning: { enabled }` and also `reasoning_effort` when `supportsReasoningEffort` is enabled. `qwen` uses top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that require `chat_template_kwargs.enable_thinking` and `preserve_thinking`. Use `chat-template` for vLLM/Hugging Face chat templates that need configurable `chat_template_kwargs`, such as `chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } }` for DeepSeek V3.x templates.
`cacheControlFormat: "anthropic"` is for OpenAI-compatible providers that expose Anthropic-style prompt caching through `cache_control` markers on text content and tool definitions.
diff --git a/packages/coding-agent/docs/packages.md b/packages/coding-agent/docs/packages.md
index 7009b773..73f2dccb 100644
--- a/packages/coding-agent/docs/packages.md
+++ b/packages/coding-agent/docs/packages.md
@@ -28,7 +28,8 @@ pi install ./relative/path/to/package
pi remove npm:@foo/bar
pi list # show installed packages from settings
-pi update # update pi, update packages, and reconcile pinned git refs
+pi update # update pi only
+pi update --all # update pi, update packages, and reconcile pinned git refs
pi update --extensions # update packages and reconcile pinned git refs only
pi update --self # update pi only
pi update --self --force # reinstall pi even if current
@@ -36,7 +37,7 @@ pi update npm:@foo/bar # update one package
pi update --extension npm:@foo/bar
```
-These commands manage pi packages, not the pi CLI installation. To uninstall pi itself, see [Quickstart](quickstart.md#uninstall).
+These commands manage pi packages and `pi update` can update the pi CLI installation. To uninstall pi itself, see [Quickstart](quickstart.md#uninstall).
By default, `install` and `remove` write to user settings (`~/.pi/agent/settings.json`). Use `-l` to write to project settings (`.pi/settings.json`) instead. Project settings can be shared with your team, and pi installs any missing packages automatically on startup after the project is trusted.
@@ -58,7 +59,7 @@ npm:@scope/pkg@1.2.3
npm:pkg
```
-- Versioned specs are pinned and skipped by package updates (`pi update`, `pi update --extensions`).
+- Versioned specs are pinned and skipped by package updates (`pi update --extensions`, `pi update --all`).
- User installs go under `~/.pi/agent/npm/`.
- Project installs go under `.pi/npm/`.
- Set `npmCommand` in `settings.json` to pin npm package lookup and install operations to a specific wrapper command such as `mise` or `asdf`.
@@ -85,7 +86,7 @@ ssh://git@github.com/user/repo@v1
- HTTPS and SSH URLs are both supported.
- SSH URLs use your configured SSH keys automatically (respects `~/.ssh/config`).
- For non-interactive runs (for example CI), you can set `GIT_TERMINAL_PROMPT=0` to disable credential prompts and set `GIT_SSH_COMMAND` (for example `ssh -o BatchMode=yes -o ConnectTimeout=5`) to fail fast.
-- Refs are pinned tags or commits. `pi update` and `pi update --extensions` do not move them to newer refs, but they do reconcile an existing clone to the configured ref.
+- Refs are pinned tags or commits. `pi update --extensions` and `pi update --all` do not move them to newer refs, but they do reconcile an existing clone to the configured ref.
- Use `pi install git:host/user/repo@new-ref` to update settings and move an existing package to a new pinned ref.
- Cloned to `~/.pi/agent/git//` (global) or `.pi/git//` (project).
- When reconciliation changes the checkout, pi resets and cleans the clone, then runs `npm install` if `package.json` exists.
diff --git a/packages/coding-agent/docs/providers.md b/packages/coding-agent/docs/providers.md
index 185405cd..86f1de06 100644
--- a/packages/coding-agent/docs/providers.md
+++ b/packages/coding-agent/docs/providers.md
@@ -104,6 +104,24 @@ Store credentials in `~/.pi/agent/auth.json`:
The file is created with `0600` permissions (user read/write only). Auth file credentials take priority over environment variables.
+API key credentials can also include provider-scoped environment values. These values are used before process environment variables when resolving the credential key, provider/model headers, and provider configuration such as Cloudflare account IDs, Azure OpenAI settings, Vertex project/location, Bedrock settings, `PI_CACHE_RETENTION`, and `HTTP_PROXY`/`HTTPS_PROXY`.
+
+```json
+{
+ "cloudflare-ai-gateway": {
+ "type": "api_key",
+ "key": "$CLOUDFLARE_API_KEY",
+ "env": {
+ "CLOUDFLARE_API_KEY": "...",
+ "CLOUDFLARE_ACCOUNT_ID": "account-id",
+ "CLOUDFLARE_GATEWAY_ID": "gateway-id"
+ }
+ }
+}
+```
+
+Use this when pi should use different provider settings than the project shell environment.
+
### Key Resolution
The `key` field supports command execution, environment interpolation, and literals:
@@ -124,13 +142,13 @@ The `key` field supports command execution, environment interpolation, and liter
{ "type": "api_key", "key": "$$literal-dollar-prefix" }
{ "type": "api_key", "key": "$!literal-bang-prefix" }
```
-- **Literal value:** Used directly
+- **Literal value:** Used directly. Plain uppercase strings such as `MY_API_KEY` are literals; use `$MY_API_KEY` for environment variables.
```json
{ "type": "api_key", "key": "sk-ant-..." }
{ "type": "api_key", "key": "public" }
```
-Legacy uppercase env-var-like values such as `MY_API_KEY` are migrated to `$MY_API_KEY` on startup. OAuth credentials are also stored here after `/login` and managed automatically.
+OAuth credentials are also stored here after `/login` and managed automatically.
## Cloud Providers
@@ -194,7 +212,7 @@ export AWS_BEDROCK_FORCE_HTTP1=1
### Cloudflare AI Gateway
-`CLOUDFLARE_API_KEY` can be set via `/login`. The account ID and gateway slug must be set as environment variables.
+`CLOUDFLARE_API_KEY` can be set via `/login`. The account ID and gateway slug can be set as environment variables or in the API key credential's `env` object in `auth.json`.
```bash
export CLOUDFLARE_API_KEY=... # or use /login
@@ -218,7 +236,7 @@ For normal pi usage, prefer unified billing or stored BYOK. Inline BYOK requires
### Cloudflare Workers AI
-`CLOUDFLARE_API_KEY` can be set via `/login`. `CLOUDFLARE_ACCOUNT_ID` must be set as an environment variable.
+`CLOUDFLARE_API_KEY` can be set via `/login`. `CLOUDFLARE_ACCOUNT_ID` can be set as an environment variable or in the API key credential's `env` object in `auth.json`.
```bash
export CLOUDFLARE_API_KEY=... # or use /login
diff --git a/packages/coding-agent/docs/rpc.md b/packages/coding-agent/docs/rpc.md
index 9aa16ffc..a9942409 100644
--- a/packages/coding-agent/docs/rpc.md
+++ b/packages/coding-agent/docs/rpc.md
@@ -374,11 +374,14 @@ Response:
"summary": "Summary of conversation...",
"firstKeptEntryId": "abc123",
"tokensBefore": 150000,
+ "estimatedTokensAfter": 32000,
"details": {}
}
}
```
+`estimatedTokensAfter` is a heuristic estimate over the rebuilt message context immediately after compaction, not a provider-exact token count.
+
#### set_auto_compaction
Enable or disable automatic compaction when context is nearly full.
@@ -924,6 +927,7 @@ The `reason` field is `"manual"`, `"threshold"`, or `"overflow"`.
"summary": "Summary of conversation...",
"firstKeptEntryId": "abc123",
"tokensBefore": 150000,
+ "estimatedTokensAfter": 32000,
"details": {}
},
"aborted": false,
diff --git a/packages/coding-agent/docs/sdk.md b/packages/coding-agent/docs/sdk.md
index c6b2a75d..0c521e74 100644
--- a/packages/coding-agent/docs/sdk.md
+++ b/packages/coding-agent/docs/sdk.md
@@ -1110,7 +1110,8 @@ DefaultResourceLoader
type ResourceLoader
createEventBus
-// Helpers
+// Constants and helpers
+CONFIG_DIR_NAME
defineTool
getAgentDir
getPackageDir
diff --git a/packages/coding-agent/docs/security.md b/packages/coding-agent/docs/security.md
index 0c6d387a..29828e03 100644
--- a/packages/coding-agent/docs/security.md
+++ b/packages/coding-agent/docs/security.md
@@ -6,14 +6,18 @@ Pi is a local coding agent. It runs with the permissions of the user account tha
Project trust controls whether pi loads project-local settings, resources, packages, and extensions. It is not a sandbox and it does not restrict what the model can ask tools to do after you start working in a directory.
-Pi considers a project to have trust inputs when it finds any of these from the current working directory:
+Pi considers a project to have resources that require trust when it finds any of these from the current working directory:
-- `.pi/` in the current directory
-- `.agents/skills` in the current directory or an ancestor directory
+- `.pi/settings.json`
+- `.pi/extensions`, `.pi/skills`, `.pi/prompts`, or `.pi/themes`
+- `.pi/SYSTEM.md` or `.pi/APPEND_SYSTEM.md`
+- project `.agents/skills` in the current directory or an ancestor directory
-When an interactive session starts in a project with configs in `.pi` or `.agents/skills` and no saved decision for the current directory or a parent directory, pi follows `defaultProjectTrust` from global settings. The default value is `"ask"`, which asks whether to trust the project when UI is available. Saved decisions are stored by canonical directory in `~/.pi/agent/trust.json`, and the closest saved decision on the current or parent path applies before the global default.
+A bare `.pi` directory does not count as a project resource that requires trust.
-Trusting a project allows pi to load trust-gated project inputs, including:
+When an interactive session starts in a project with resources that require trust and no saved decision for the current directory or a parent directory, pi follows `defaultProjectTrust` from global settings. The default value is `"ask"`, which asks whether to trust the project when UI is available. Saved decisions are stored by canonical directory in `~/.pi/agent/trust.json`, and the closest saved decision on the current or parent path applies before the global default.
+
+Trusting a project allows pi to load project resources that require trust, including:
- `.pi/settings.json`
- `.pi` resources such as extensions, skills, prompt templates, themes, and system prompt files
@@ -38,7 +42,7 @@ For untrusted repositories, generated code you do not intend to monitor closely,
Common patterns are documented in [Containerization](containerization.md):
-- run the whole `pi` process inside OpenShell or Docker
+- run the whole `pi` process inside a container/sandbox
- run host pi while routing built-in tool execution into a Gondolin micro-VM
- mount only the workspace paths the agent should access
- avoid mounting host `~/.pi/agent` unless the container should access host sessions, settings, and credentials
diff --git a/packages/coding-agent/docs/settings.md b/packages/coding-agent/docs/settings.md
index 2cf843d1..18c9ad97 100644
--- a/packages/coding-agent/docs/settings.md
+++ b/packages/coding-agent/docs/settings.md
@@ -11,13 +11,13 @@ Edit directly or use `/settings` for common options.
## Project Trust
-On interactive startup, pi asks before trusting a project folder that contains trust-gated project inputs and has no saved decision for the folder or a parent folder in `~/.pi/agent/trust.json`. Trusting a project allows pi to load `.pi/settings.json` and `.pi` resources, install missing project packages, and execute project extensions.
+On interactive startup, pi asks before trusting a project folder that contains project-local settings, resources, or project `.agents/skills` and has no saved decision for the folder or a parent folder in `~/.pi/agent/trust.json`. Trusting a project allows pi to load `.pi/settings.json` and `.pi` resources, install missing project packages, and execute project extensions.
-Non-interactive modes (`-p`, `--mode json`, and `--mode rpc`) do not show a trust prompt. Without an applicable saved trust decision, they use `defaultProjectTrust` from global settings: `ask` (default) and `never` ignore trust-gated project inputs, while `always` trusts them. Pass `--approve`/`-a` or `--no-approve`/`-na` to override project trust for one run.
+Non-interactive modes (`-p`, `--mode json`, and `--mode rpc`) do not show a trust prompt. Without an applicable saved trust decision, they use `defaultProjectTrust` from global settings: `ask` (default) and `never` ignore those project resources, while `always` trusts them. Pass `--approve`/`-a` or `--no-approve`/`-na` to override project trust for one run.
If no extension or saved decision applies, `defaultProjectTrust` controls the fallback behavior. Set it to `"ask"`, `"always"`, or `"never"` in `~/.pi/agent/settings.json`, or change it with `/settings`.
-`pi config` and package commands use the same project trust flow. Pass `--approve` to trust project-local settings for one command or `--no-approve` to ignore them.
+`pi config` and package commands use the same project trust flow, except `pi update` never prompts. Pass `--approve` to trust project-local settings for one command or `--no-approve` to ignore them.
Use `/trust` in interactive mode to save a project trust decision for future sessions, including trust for the immediate parent folder. It writes `~/.pi/agent/trust.json` only; the current session is not reloaded, so restart pi for changes to take effect.
@@ -69,6 +69,18 @@ Use `/trust` in interactive mode to save a project trust decision for future ses
Set `PI_SKIP_VERSION_CHECK=1` to disable the Pi version update check. Use `--offline` or `PI_OFFLINE=1` to disable all startup network operations described here, including update checks, package update checks, and install/update telemetry.
+### Network
+
+| Setting | Type | Default | Description |
+|---------|------|---------|-------------|
+| `httpProxy` | string | - | HTTP proxy URL applied as `HTTP_PROXY` and `HTTPS_PROXY`. Global setting only. |
+
+```json
+{
+ "httpProxy": "http://127.0.0.1:7890"
+}
+```
+
### Warnings
| Setting | Type | Default | Description |
diff --git a/packages/coding-agent/docs/themes.md b/packages/coding-agent/docs/themes.md
index c18e954b..11655128 100644
--- a/packages/coding-agent/docs/themes.md
+++ b/packages/coding-agent/docs/themes.md
@@ -137,7 +137,7 @@ vim ~/.pi/agent/themes/my-theme.json
}
```
-- `name` is required and must be unique.
+- `name` is required, must be unique, and must not contain `/`.
- `vars` is optional. Define reusable colors here, then reference them in `colors`.
- `colors` must define all 51 required tokens.
diff --git a/packages/coding-agent/docs/tui.md b/packages/coding-agent/docs/tui.md
index c3b31204..38ef1986 100644
--- a/packages/coding-agent/docs/tui.md
+++ b/packages/coding-agent/docs/tui.md
@@ -257,7 +257,7 @@ md.setText("Updated markdown");
### Image
-Renders images in supported terminals (Kitty, iTerm2, Ghostty, WezTerm).
+Renders images in supported terminals (Kitty, iTerm2, Ghostty, WezTerm, Warp).
```typescript
const image = new Image(
@@ -742,7 +742,7 @@ ctx.ui.setStatus("my-ext", ctx.ui.theme.fg("accent", "● active"));
ctx.ui.setStatus("my-ext", undefined);
```
-**Examples:** [status-line.ts](../examples/extensions/status-line.ts), [plan-mode.ts](../examples/extensions/plan-mode.ts), [preset.ts](../examples/extensions/preset.ts)
+**Examples:** [status-line.ts](../examples/extensions/status-line.ts), [plan-mode/index.ts](../examples/extensions/plan-mode/index.ts), [preset.ts](../examples/extensions/preset.ts)
### Pattern 4b: Working Indicator Customization
@@ -802,7 +802,7 @@ ctx.ui.setWidget("my-widget", (_tui, theme) => {
ctx.ui.setWidget("my-widget", undefined);
```
-**Examples:** [plan-mode.ts](../examples/extensions/plan-mode.ts)
+**Examples:** [plan-mode/index.ts](../examples/extensions/plan-mode/index.ts)
### Pattern 6: Custom Footer
@@ -919,7 +919,7 @@ export default function (pi: ExtensionAPI) {
- **Selection UI**: [examples/extensions/preset.ts](../examples/extensions/preset.ts) - SelectList with DynamicBorder framing
- **Async with cancel**: [examples/extensions/qna.ts](../examples/extensions/qna.ts) - BorderedLoader for LLM calls
- **Settings toggles**: [examples/extensions/tools.ts](../examples/extensions/tools.ts) - SettingsList for tool enable/disable
-- **Status indicators**: [examples/extensions/plan-mode.ts](../examples/extensions/plan-mode.ts) - setStatus and setWidget
+- **Status indicators**: [examples/extensions/plan-mode/index.ts](../examples/extensions/plan-mode/index.ts) - setStatus and setWidget
- **Working indicator**: [examples/extensions/working-indicator.ts](../examples/extensions/working-indicator.ts) - setWorkingIndicator
- **Custom footer**: [examples/extensions/custom-footer.ts](../examples/extensions/custom-footer.ts) - setFooter with stats
- **Custom editor**: [examples/extensions/modal-editor.ts](../examples/extensions/modal-editor.ts) - Vim-like modal editing
diff --git a/packages/coding-agent/docs/usage.md b/packages/coding-agent/docs/usage.md
index bb8a4c25..ccd3ee41 100644
--- a/packages/coding-agent/docs/usage.md
+++ b/packages/coding-agent/docs/usage.md
@@ -44,11 +44,13 @@ Type `/` in the editor to open command completion. Extensions can register custo
| `/name ` | Set session display name |
| `/session` | Show session file, ID, messages, tokens, and cost |
| `/tree` | Jump to any point in the session and continue from there |
+| `/trust` | Save project trust decision for future sessions |
| `/fork` | Create a new session from a previous user message |
| `/clone` | Duplicate the current active branch into a new session |
| `/compact [prompt]` | Manually compact context, optionally with custom instructions |
| `/copy` | Copy last assistant message to clipboard |
-| `/export [file]` | Export session to HTML |
+| `/export [file]` | Export session to HTML or JSONL |
+| `/import ` | Import and resume a session from a JSONL file |
| `/share` | Upload as private GitHub gist with shareable HTML link |
| `/reload` | Reload keybindings, extensions, skills, prompts, and context files |
| `/hotkeys` | Show all keyboard shortcuts |
@@ -112,15 +114,15 @@ Append to the default prompt without replacing it with `APPEND_SYSTEM.md` in eit
### Project Trust
-On interactive startup, pi asks before trusting a project folder that contains project-local extensions or settings and has no saved decision for the folder or a parent folder in `~/.pi/agent/trust.json`. Trusting a project allows pi to load `.pi/settings.json` and `.pi` resources, install missing project packages, and execute project extensions.
+On interactive startup, pi asks before trusting a project folder that contains project-local settings, resources, or project `.agents/skills` and has no saved decision for the folder or a parent folder in `~/.pi/agent/trust.json`. Trusting a project allows pi to load `.pi/settings.json` and `.pi` resources, install missing project packages, and execute project extensions.
Before the trust decision, pi loads only context files, user/global extensions, and CLI `-e` extensions so they can handle the `project_trust` event. Project-local extensions, project package-managed extensions, and project settings are loaded only after the project is trusted. This split also applies when switching to a session from a different cwd whose trust has not been resolved in the current process.
-Non-interactive modes (`-p`, `--mode json`, and `--mode rpc`) do not show a trust prompt. Without an applicable saved trust decision, they use `defaultProjectTrust` from global settings: `ask` (default) and `never` ignore trust-gated project inputs, while `always` trusts them. Pass `--approve`/`-a` or `--no-approve`/`-na` to override project trust for one run.
+Non-interactive modes (`-p`, `--mode json`, and `--mode rpc`) do not show a trust prompt. Without an applicable saved trust decision, they use `defaultProjectTrust` from global settings: `ask` (default) and `never` ignore those project resources, while `always` trusts them. Pass `--approve`/`-a` or `--no-approve`/`-na` to override project trust for one run.
If no extension or saved decision applies, `defaultProjectTrust` controls the fallback behavior. Set it to `"ask"`, `"always"`, or `"never"` in `~/.pi/agent/settings.json`, or change it with `/settings`.
-`pi config` and package commands use the same project trust flow. Pass `--approve` to trust project-local settings for one command or `--no-approve` to ignore them.
+`pi config` and package commands use the same project trust flow, except `pi update` never prompts. Pass `--approve` to trust project-local settings for one command or `--no-approve` to ignore them.
Use `/trust` in interactive mode to save a project trust decision for future sessions, including trust for the immediate parent folder. It writes `~/.pi/agent/trust.json` only; the current session is not reloaded, so restart pi for changes to take effect.
@@ -145,7 +147,8 @@ pi [options] [@files...] [messages...]
pi install [-l] # Install package, -l for project-local
pi remove [-l] # Remove package
pi uninstall [-l] # Alias for remove
-pi update [source|self|pi] # Update pi and packages; reconcile pinned git refs
+pi update [source|self|pi] # Update pi only, or one package source
+pi update --all # Update pi and packages; reconcile pinned git refs
pi update --extensions # Update packages only; reconcile pinned git refs
pi update --self # Update pi only
pi update --extension # Update one package
@@ -153,7 +156,7 @@ pi list # List installed packages
pi config # Enable/disable package resources
```
-These commands manage pi packages, not the pi CLI installation. To uninstall pi itself, see [Quickstart](quickstart.md#uninstall). `pi config` and project package commands accept `--approve`/`--no-approve` to trust or ignore project-local settings for one command.
+These commands manage pi packages and `pi update` can update the pi CLI installation. To uninstall pi itself, see [Quickstart](quickstart.md#uninstall). `pi config` and project package commands accept `--approve`/`--no-approve` to trust or ignore project-local settings for one command. `pi update` never prompts for project trust.
See [Pi Packages](packages.md) for package sources and security notes.
diff --git a/packages/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json b/packages/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json
index dc287898..c138594f 100644
--- a/packages/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json
+++ b/packages/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "pi-extension-custom-provider",
- "version": "0.79.1",
+ "version": "0.79.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pi-extension-custom-provider",
- "version": "0.79.1",
+ "version": "0.79.10",
"dependencies": {
"@anthropic-ai/sdk": "^0.52.0"
}
diff --git a/packages/coding-agent/examples/extensions/custom-provider-anthropic/package.json b/packages/coding-agent/examples/extensions/custom-provider-anthropic/package.json
index 4b1dd16b..11dfb54a 100644
--- a/packages/coding-agent/examples/extensions/custom-provider-anthropic/package.json
+++ b/packages/coding-agent/examples/extensions/custom-provider-anthropic/package.json
@@ -1,7 +1,7 @@
{
"name": "pi-extension-custom-provider-anthropic",
"private": true,
- "version": "0.79.1",
+ "version": "0.79.10",
"type": "module",
"scripts": {
"clean": "echo 'nothing to clean'",
diff --git a/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json b/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json
index fba74f25..a1d1808b 100644
--- a/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json
+++ b/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json
@@ -1,7 +1,7 @@
{
"name": "pi-extension-custom-provider-gitlab-duo",
"private": true,
- "version": "0.79.1",
+ "version": "0.79.10",
"type": "module",
"scripts": {
"clean": "echo 'nothing to clean'",
diff --git a/packages/coding-agent/examples/extensions/gondolin/package-lock.json b/packages/coding-agent/examples/extensions/gondolin/package-lock.json
index b71b386c..7d7f5932 100644
--- a/packages/coding-agent/examples/extensions/gondolin/package-lock.json
+++ b/packages/coding-agent/examples/extensions/gondolin/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "pi-extension-gondolin",
- "version": "0.79.1",
+ "version": "0.79.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pi-extension-gondolin",
- "version": "0.79.1",
+ "version": "0.79.10",
"dependencies": {
"@earendil-works/gondolin": "0.12.0"
}
diff --git a/packages/coding-agent/examples/extensions/gondolin/package.json b/packages/coding-agent/examples/extensions/gondolin/package.json
index 51448000..e473cb1a 100644
--- a/packages/coding-agent/examples/extensions/gondolin/package.json
+++ b/packages/coding-agent/examples/extensions/gondolin/package.json
@@ -1,7 +1,7 @@
{
"name": "pi-extension-gondolin",
"private": true,
- "version": "0.79.1",
+ "version": "0.79.10",
"type": "module",
"scripts": {
"clean": "echo 'nothing to clean'",
diff --git a/packages/coding-agent/examples/extensions/plan-mode/README.md b/packages/coding-agent/examples/extensions/plan-mode/README.md
index 549e3473..2568a684 100644
--- a/packages/coding-agent/examples/extensions/plan-mode/README.md
+++ b/packages/coding-agent/examples/extensions/plan-mode/README.md
@@ -4,7 +4,7 @@ Read-only exploration mode for safe code analysis.
## Features
-- **Read-only tools**: Restricts available tools to read, bash, grep, find, ls, question
+- **Built-in write tools disabled**: Disables edit/write while preserving other active tools
- **Bash allowlist**: Only read-only bash commands are allowed
- **Plan extraction**: Extracts numbered steps from `Plan:` sections
- **Progress tracking**: Widget shows completion status during execution
@@ -37,7 +37,8 @@ Plan:
## How It Works
### Plan Mode (Read-Only)
-- Only read-only tools available
+- Built-in edit/write tools disabled
+- Other active tools remain available
- Bash commands filtered through allowlist
- Agent creates a plan without making changes
diff --git a/packages/coding-agent/examples/extensions/plan-mode/index.ts b/packages/coding-agent/examples/extensions/plan-mode/index.ts
index 40db408c..737ce56a 100644
--- a/packages/coding-agent/examples/extensions/plan-mode/index.ts
+++ b/packages/coding-agent/examples/extensions/plan-mode/index.ts
@@ -2,7 +2,7 @@
* Plan Mode Extension
*
* Read-only exploration mode for safe code analysis.
- * When enabled, only read-only tools are available.
+ * When enabled, built-in write tools are disabled.
*
* Features:
* - /plan command or Ctrl+Alt+P to toggle
@@ -21,6 +21,15 @@ import { extractTodoItems, isSafeCommand, markCompletedSteps, type TodoItem } fr
// Tools
const PLAN_MODE_TOOLS = ["read", "bash", "grep", "find", "ls", "questionnaire"];
const NORMAL_MODE_TOOLS = ["read", "bash", "edit", "write"];
+const PLAN_MODE_DISABLED_TOOLS = new Set(["edit", "write"]);
+const PLAN_MANAGED_TOOLS = new Set([...PLAN_MODE_TOOLS, ...NORMAL_MODE_TOOLS]);
+
+interface PlanModeState {
+ enabled: boolean;
+ todos?: TodoItem[];
+ executing?: boolean;
+ toolsBeforePlanMode?: string[];
+}
// Type guard for assistant messages
function isAssistantMessage(m: AgentMessage): m is AssistantMessage {
@@ -39,6 +48,7 @@ export default function planModeExtension(pi: ExtensionAPI): void {
let planModeEnabled = false;
let executionMode = false;
let todoItems: TodoItem[] = [];
+ let toolsBeforePlanMode: string[] | undefined;
pi.registerFlag("plan", {
description: "Start in plan mode (read-only exploration)",
@@ -73,19 +83,34 @@ export default function planModeExtension(pi: ExtensionAPI): void {
}
}
- function togglePlanMode(ctx: ExtensionContext): void {
- planModeEnabled = !planModeEnabled;
- executionMode = false;
- todoItems = [];
+ function uniqueToolNames(toolNames: string[]): string[] {
+ return [...new Set(toolNames)];
+ }
- if (planModeEnabled) {
- pi.setActiveTools(PLAN_MODE_TOOLS);
- ctx.ui.notify(`Plan mode enabled. Tools: ${PLAN_MODE_TOOLS.join(", ")}`);
- } else {
- pi.setActiveTools(NORMAL_MODE_TOOLS);
- ctx.ui.notify("Plan mode disabled. Full access restored.");
+ function getPlanModeTools(activeToolNames: string[]): string[] {
+ return uniqueToolNames([
+ ...activeToolNames.filter((name) => !PLAN_MODE_DISABLED_TOOLS.has(name)),
+ ...PLAN_MODE_TOOLS,
+ ]);
+ }
+
+ function getNormalModeTools(activeToolNames: string[]): string[] {
+ return uniqueToolNames([
+ ...NORMAL_MODE_TOOLS,
+ ...activeToolNames.filter((name) => !PLAN_MANAGED_TOOLS.has(name)),
+ ]);
+ }
+
+ function enablePlanModeTools(): void {
+ if (toolsBeforePlanMode === undefined) {
+ toolsBeforePlanMode = pi.getActiveTools();
}
- updateStatus(ctx);
+ pi.setActiveTools(getPlanModeTools(toolsBeforePlanMode));
+ }
+
+ function restoreNormalModeTools(): void {
+ pi.setActiveTools(toolsBeforePlanMode ?? getNormalModeTools(pi.getActiveTools()));
+ toolsBeforePlanMode = undefined;
}
function persistState(): void {
@@ -93,9 +118,26 @@ export default function planModeExtension(pi: ExtensionAPI): void {
enabled: planModeEnabled,
todos: todoItems,
executing: executionMode,
+ toolsBeforePlanMode,
});
}
+ function togglePlanMode(ctx: ExtensionContext): void {
+ planModeEnabled = !planModeEnabled;
+ executionMode = false;
+ todoItems = [];
+
+ if (planModeEnabled) {
+ enablePlanModeTools();
+ ctx.ui.notify("Plan mode enabled. Built-in write tools disabled.");
+ } else {
+ restoreNormalModeTools();
+ ctx.ui.notify("Plan mode disabled. Full access restored.");
+ }
+ updateStatus(ctx);
+ persistState();
+ }
+
pi.registerCommand("plan", {
description: "Toggle plan mode (read-only exploration)",
handler: async (_args, ctx) => togglePlanMode(ctx),
@@ -165,8 +207,8 @@ export default function planModeExtension(pi: ExtensionAPI): void {
You are in plan mode - a read-only exploration mode for safe code analysis.
Restrictions:
-- You can only use: read, bash, grep, find, ls, questionnaire
-- You CANNOT use: edit, write (file modifications are disabled)
+- Built-in edit and write tools are disabled
+- Other currently active tools remain available
- Bash is restricted to an allowlist of read-only commands
Ask clarifying questions using the questionnaire tool.
@@ -228,7 +270,6 @@ After completing a step, include a [DONE:n] tag in your response.`,
);
executionMode = false;
todoItems = [];
- pi.setActiveTools(NORMAL_MODE_TOOLS);
updateStatus(ctx);
persistState(); // Save cleared state so resume doesn't restore old execution mode
}
@@ -246,43 +287,51 @@ After completing a step, include a [DONE:n] tag in your response.`,
}
}
+ if (todoItems.length === 0) return;
+ persistState();
+
// Show plan steps and prompt for next action
- if (todoItems.length > 0) {
- const todoListText = todoItems.map((t, i) => `${i + 1}. ☐ ${t.text}`).join("\n");
- pi.sendMessage(
- {
- customType: "plan-todo-list",
- content: `**Plan Steps (${todoItems.length}):**\n\n${todoListText}`,
- display: true,
- },
- { triggerTurn: false },
- );
- }
+ const todoListText = todoItems.map((t, i) => `${i + 1}. ☐ ${t.text}`).join("\n");
+ const planTodoListMessage = {
+ customType: "plan-todo-list",
+ content: `**Plan Steps (${todoItems.length}):**\n\n${todoListText}`,
+ display: true,
+ };
const choice = await ctx.ui.select("Plan mode - what next?", [
- todoItems.length > 0 ? "Execute the plan (track progress)" : "Execute the plan",
+ "Execute the plan (track progress)",
"Stay in plan mode",
"Refine the plan",
]);
if (choice?.startsWith("Execute")) {
- planModeEnabled = false;
- executionMode = todoItems.length > 0;
- pi.setActiveTools(NORMAL_MODE_TOOLS);
- updateStatus(ctx);
+ const firstTodoItem = todoItems[0];
+ if (!firstTodoItem) return;
- const execMessage =
- todoItems.length > 0
- ? `Execute the plan. Start with: ${todoItems[0].text}`
- : "Execute the plan you just created.";
+ planModeEnabled = false;
+ executionMode = true;
+ restoreNormalModeTools();
+ updateStatus(ctx);
+ persistState();
+
+ const remainingList = todoItems.map((t) => `${t.step}. ${t.text}`).join("\n");
+ const execMessage = `Execute the plan.
+
+Remaining steps:
+${remainingList}
+
+Start with: ${firstTodoItem.text}
+After completing a step, include a [DONE:n] tag in your response.`;
+ pi.sendMessage(planTodoListMessage, { deliverAs: "followUp" });
pi.sendMessage(
{ customType: "plan-mode-execute", content: execMessage, display: true },
- { triggerTurn: true },
+ { triggerTurn: true, deliverAs: "followUp" },
);
} else if (choice === "Refine the plan") {
const refinement = await ctx.ui.editor("Refine the plan:", "");
if (refinement?.trim()) {
- pi.sendUserMessage(refinement.trim());
+ pi.sendMessage(planTodoListMessage, { deliverAs: "followUp" });
+ pi.sendUserMessage(refinement.trim(), { deliverAs: "followUp" });
}
}
});
@@ -298,12 +347,13 @@ After completing a step, include a [DONE:n] tag in your response.`,
// Restore persisted state
const planModeEntry = entries
.filter((e: { type: string; customType?: string }) => e.type === "custom" && e.customType === "plan-mode")
- .pop() as { data?: { enabled: boolean; todos?: TodoItem[]; executing?: boolean } } | undefined;
+ .pop() as { data?: PlanModeState } | undefined;
if (planModeEntry?.data) {
planModeEnabled = planModeEntry.data.enabled ?? planModeEnabled;
todoItems = planModeEntry.data.todos ?? todoItems;
executionMode = planModeEntry.data.executing ?? executionMode;
+ toolsBeforePlanMode = planModeEntry.data.toolsBeforePlanMode ?? toolsBeforePlanMode;
}
// On resume: re-scan messages to rebuild completion state
@@ -333,7 +383,7 @@ After completing a step, include a [DONE:n] tag in your response.`,
}
if (planModeEnabled) {
- pi.setActiveTools(PLAN_MODE_TOOLS);
+ enablePlanModeTools();
}
updateStatus(ctx);
});
diff --git a/packages/coding-agent/examples/extensions/preset.ts b/packages/coding-agent/examples/extensions/preset.ts
index 92224ec2..b78237ca 100644
--- a/packages/coding-agent/examples/extensions/preset.ts
+++ b/packages/coding-agent/examples/extensions/preset.ts
@@ -42,7 +42,7 @@ import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import type { Api, Model } from "@earendil-works/pi-ai";
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
-import { DynamicBorder, getAgentDir } from "@earendil-works/pi-coding-agent";
+import { CONFIG_DIR_NAME, DynamicBorder, getAgentDir } from "@earendil-works/pi-coding-agent";
import { Container, Key, type SelectItem, SelectList, Text } from "@earendil-works/pi-tui";
// Preset configuration
@@ -69,7 +69,7 @@ interface PresetsConfig {
*/
function loadPresets(cwd: string): PresetsConfig {
const globalPath = join(getAgentDir(), "presets.json");
- const projectPath = join(cwd, ".pi", "presets.json");
+ const projectPath = join(cwd, CONFIG_DIR_NAME, "presets.json");
let globalPresets: PresetsConfig = {};
let projectPresets: PresetsConfig = {};
@@ -200,7 +200,10 @@ export default function presetExtension(pi: ExtensionAPI) {
const presetNames = Object.keys(presets);
if (presetNames.length === 0) {
- ctx.ui.notify("No presets defined. Add presets to ~/.pi/agent/presets.json or .pi/presets.json", "warning");
+ ctx.ui.notify(
+ `No presets defined. Add presets to ${join(getAgentDir(), "presets.json")} or ${join(ctx.cwd, CONFIG_DIR_NAME, "presets.json")}`,
+ "warning",
+ );
return;
}
@@ -308,7 +311,10 @@ export default function presetExtension(pi: ExtensionAPI) {
async function cyclePreset(ctx: ExtensionContext): Promise {
const presetNames = getPresetOrder();
if (presetNames.length === 0) {
- ctx.ui.notify("No presets defined. Add presets to ~/.pi/agent/presets.json or .pi/presets.json", "warning");
+ ctx.ui.notify(
+ `No presets defined. Add presets to ${join(getAgentDir(), "presets.json")} or ${join(ctx.cwd, CONFIG_DIR_NAME, "presets.json")}`,
+ "warning",
+ );
return;
}
diff --git a/packages/coding-agent/examples/extensions/provider-payload.ts b/packages/coding-agent/examples/extensions/provider-payload.ts
index 860ddc00..7f02a077 100644
--- a/packages/coding-agent/examples/extensions/provider-payload.ts
+++ b/packages/coding-agent/examples/extensions/provider-payload.ts
@@ -1,18 +1,18 @@
import { appendFileSync } from "node:fs";
import { join } from "node:path";
-import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
+import { CONFIG_DIR_NAME, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
export default function (pi: ExtensionAPI) {
- const logFile = join(process.cwd(), ".pi", "provider-payload.log");
-
- pi.on("before_provider_request", (event) => {
+ pi.on("before_provider_request", (event, ctx) => {
+ const logFile = join(ctx.cwd, CONFIG_DIR_NAME, "provider-payload.log");
appendFileSync(logFile, `${JSON.stringify(event.payload, null, 2)}\n\n`, "utf8");
// Optional: replace the payload instead of only logging it.
// return { ...event.payload, temperature: 0 };
});
- pi.on("after_provider_response", (event) => {
+ pi.on("after_provider_response", (event, ctx) => {
+ const logFile = join(ctx.cwd, CONFIG_DIR_NAME, "provider-payload.log");
appendFileSync(logFile, `[${event.status}] ${JSON.stringify(event.headers)}\n\n`, "utf8");
});
}
diff --git a/packages/coding-agent/examples/extensions/question.ts b/packages/coding-agent/examples/extensions/question.ts
index eb1af51a..1192b490 100644
--- a/packages/coding-agent/examples/extensions/question.ts
+++ b/packages/coding-agent/examples/extensions/question.ts
@@ -5,7 +5,15 @@
*/
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
-import { Editor, type EditorTheme, Key, matchesKey, Text, truncateToWidth } from "@earendil-works/pi-tui";
+import {
+ Editor,
+ type EditorTheme,
+ Key,
+ matchesKey,
+ Text,
+ visibleWidth,
+ wrapTextWithAnsi,
+} from "@earendil-works/pi-tui";
import { Type } from "typebox";
interface OptionWithDesc {
@@ -139,10 +147,27 @@ export default function question(pi: ExtensionAPI) {
if (cachedLines) return cachedLines;
const lines: string[] = [];
- const add = (s: string) => lines.push(truncateToWidth(s, width));
+ const renderWidth = Math.max(1, width);
- add(theme.fg("accent", "─".repeat(width)));
- add(theme.fg("text", ` ${params.question}`));
+ function addWrapped(text: string) {
+ lines.push(...wrapTextWithAnsi(text, renderWidth));
+ }
+
+ function addWrappedWithPrefix(prefix: string, text: string) {
+ const prefixWidth = visibleWidth(prefix);
+ if (prefixWidth >= renderWidth) {
+ addWrapped(prefix + text);
+ return;
+ }
+ const wrapped = wrapTextWithAnsi(text, renderWidth - prefixWidth);
+ const continuationPrefix = " ".repeat(prefixWidth);
+ for (let i = 0; i < wrapped.length; i++) {
+ lines.push(`${i === 0 ? prefix : continuationPrefix}${wrapped[i]}`);
+ }
+ }
+
+ lines.push(theme.fg("accent", "─".repeat(renderWidth)));
+ addWrappedWithPrefix(" ", theme.fg("text", params.question));
lines.push("");
for (let i = 0; i < allOptions.length; i++) {
@@ -150,36 +175,32 @@ export default function question(pi: ExtensionAPI) {
const selected = i === optionIndex;
const isOther = opt.isOther === true;
const prefix = selected ? theme.fg("accent", "> ") : " ";
+ const label = `${i + 1}. ${opt.label}${isOther && editMode ? " ✎" : ""}`;
+ const color = selected || (isOther && editMode) ? "accent" : "text";
- if (isOther && editMode) {
- add(prefix + theme.fg("accent", `${i + 1}. ${opt.label} ✎`));
- } else if (selected) {
- add(prefix + theme.fg("accent", `${i + 1}. ${opt.label}`));
- } else {
- add(` ${theme.fg("text", `${i + 1}. ${opt.label}`)}`);
- }
+ addWrappedWithPrefix(prefix, theme.fg(color, label));
// Show description if present
if (opt.description) {
- add(` ${theme.fg("muted", opt.description)}`);
+ addWrappedWithPrefix(" ", theme.fg("muted", opt.description));
}
}
if (editMode) {
lines.push("");
- add(theme.fg("muted", " Your answer:"));
- for (const line of editor.render(width - 2)) {
- add(` ${line}`);
+ addWrappedWithPrefix(" ", theme.fg("muted", "Your answer:"));
+ for (const line of editor.render(Math.max(1, renderWidth - 2))) {
+ lines.push(` ${line}`);
}
}
lines.push("");
if (editMode) {
- add(theme.fg("dim", " Enter to submit • Esc to go back"));
+ addWrappedWithPrefix(" ", theme.fg("dim", "Enter to submit • Esc to go back"));
} else {
- add(theme.fg("dim", " ↑↓ navigate • Enter to select • Esc to cancel"));
+ addWrappedWithPrefix(" ", theme.fg("dim", "↑↓ navigate • Enter to select • Esc to cancel"));
}
- add(theme.fg("accent", "─".repeat(width)));
+ lines.push(theme.fg("accent", "─".repeat(renderWidth)));
cachedLines = lines;
return lines;
diff --git a/packages/coding-agent/examples/extensions/questionnaire.ts b/packages/coding-agent/examples/extensions/questionnaire.ts
index 653bd864..3a546ac3 100644
--- a/packages/coding-agent/examples/extensions/questionnaire.ts
+++ b/packages/coding-agent/examples/extensions/questionnaire.ts
@@ -6,7 +6,15 @@
*/
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
-import { Editor, type EditorTheme, Key, matchesKey, Text, truncateToWidth } from "@earendil-works/pi-tui";
+import {
+ Editor,
+ type EditorTheme,
+ Key,
+ matchesKey,
+ Text,
+ visibleWidth,
+ wrapTextWithAnsi,
+} from "@earendil-works/pi-tui";
import { Type } from "typebox";
// Types
@@ -259,13 +267,28 @@ export default function questionnaire(pi: ExtensionAPI) {
if (cachedLines) return cachedLines;
const lines: string[] = [];
+ const renderWidth = Math.max(1, width);
const q = currentQuestion();
const opts = currentOptions();
- // Helper to add truncated line
- const add = (s: string) => lines.push(truncateToWidth(s, width));
+ function addWrapped(text: string) {
+ lines.push(...wrapTextWithAnsi(text, renderWidth));
+ }
- add(theme.fg("accent", "─".repeat(width)));
+ function addWrappedWithPrefix(prefix: string, text: string) {
+ const prefixWidth = visibleWidth(prefix);
+ if (prefixWidth >= renderWidth) {
+ addWrapped(prefix + text);
+ return;
+ }
+ const wrapped = wrapTextWithAnsi(text, renderWidth - prefixWidth);
+ const continuationPrefix = " ".repeat(prefixWidth);
+ for (let i = 0; i < wrapped.length; i++) {
+ lines.push(`${i === 0 ? prefix : continuationPrefix}${wrapped[i]}`);
+ }
+ }
+
+ lines.push(theme.fg("accent", "─".repeat(renderWidth)));
// Tab bar (multi-question only)
if (isMulti) {
@@ -287,7 +310,7 @@ export default function questionnaire(pi: ExtensionAPI) {
? theme.bg("selectedBg", theme.fg("text", submitText))
: theme.fg(canSubmit ? "success" : "dim", submitText);
tabs.push(`${submitStyled} →`);
- add(` ${tabs.join("")}`);
+ addWrappedWithPrefix(" ", tabs.join(""));
lines.push("");
}
@@ -298,54 +321,52 @@ export default function questionnaire(pi: ExtensionAPI) {
const selected = i === optionIndex;
const isOther = opt.isOther === true;
const prefix = selected ? theme.fg("accent", "> ") : " ";
- const color = selected ? "accent" : "text";
- // Mark "Type something" differently when in input mode
- if (isOther && inputMode) {
- add(prefix + theme.fg("accent", `${i + 1}. ${opt.label} ✎`));
- } else {
- add(prefix + theme.fg(color, `${i + 1}. ${opt.label}`));
- }
+ const label = `${i + 1}. ${opt.label}${isOther && inputMode ? " ✎" : ""}`;
+ const color = selected || (isOther && inputMode) ? "accent" : "text";
+
+ addWrappedWithPrefix(prefix, theme.fg(color, label));
if (opt.description) {
- add(` ${theme.fg("muted", opt.description)}`);
+ addWrappedWithPrefix(" ", theme.fg("muted", opt.description));
}
}
}
// Content
if (inputMode && q) {
- add(theme.fg("text", ` ${q.prompt}`));
+ addWrappedWithPrefix(" ", theme.fg("text", q.prompt));
lines.push("");
// Show options for reference
renderOptions();
lines.push("");
- add(theme.fg("muted", " Your answer:"));
- for (const line of editor.render(width - 2)) {
- add(` ${line}`);
+ addWrappedWithPrefix(" ", theme.fg("muted", "Your answer:"));
+ for (const line of editor.render(Math.max(1, renderWidth - 2))) {
+ lines.push(` ${line}`);
}
lines.push("");
- add(theme.fg("dim", " Enter to submit • Esc to cancel"));
+ addWrappedWithPrefix(" ", theme.fg("dim", "Enter to submit • Esc to cancel"));
} else if (currentTab === questions.length) {
- add(theme.fg("accent", theme.bold(" Ready to submit")));
+ addWrappedWithPrefix(" ", theme.fg("accent", theme.bold("Ready to submit")));
lines.push("");
for (const question of questions) {
const answer = answers.get(question.id);
if (answer) {
const prefix = answer.wasCustom ? "(wrote) " : "";
- add(`${theme.fg("muted", ` ${question.label}: `)}${theme.fg("text", prefix + answer.label)}`);
+ const summary = `${theme.fg("muted", `${question.label}: `)}${theme.fg("text", prefix + answer.label)}`;
+ addWrappedWithPrefix(" ", summary);
}
}
lines.push("");
if (allAnswered()) {
- add(theme.fg("success", " Press Enter to submit"));
+ addWrappedWithPrefix(" ", theme.fg("success", "Press Enter to submit"));
} else {
const missing = questions
.filter((q) => !answers.has(q.id))
.map((q) => q.label)
.join(", ");
- add(theme.fg("warning", ` Unanswered: ${missing}`));
+ addWrappedWithPrefix(" ", theme.fg("warning", `Unanswered: ${missing}`));
}
} else if (q) {
- add(theme.fg("text", ` ${q.prompt}`));
+ addWrappedWithPrefix(" ", theme.fg("text", q.prompt));
lines.push("");
renderOptions();
}
@@ -353,11 +374,11 @@ export default function questionnaire(pi: ExtensionAPI) {
lines.push("");
if (!inputMode) {
const help = isMulti
- ? " Tab/←→ navigate • ↑↓ select • Enter confirm • Esc cancel"
- : " ↑↓ navigate • Enter select • Esc cancel";
- add(theme.fg("dim", help));
+ ? "Tab/←→ navigate • ↑↓ select • Enter confirm • Esc cancel"
+ : "↑↓ navigate • Enter select • Esc cancel";
+ addWrappedWithPrefix(" ", theme.fg("dim", help));
}
- add(theme.fg("accent", "─".repeat(width)));
+ lines.push(theme.fg("accent", "─".repeat(renderWidth)));
cachedLines = lines;
return lines;
@@ -400,7 +421,7 @@ export default function questionnaire(pi: ExtensionAPI) {
let text = theme.fg("toolTitle", theme.bold("questionnaire "));
text += theme.fg("muted", `${count} question${count !== 1 ? "s" : ""}`);
if (labels) {
- text += theme.fg("dim", ` (${truncateToWidth(labels, 40)})`);
+ text += theme.fg("dim", ` (${labels})`);
}
return new Text(text, 0, 0);
},
diff --git a/packages/coding-agent/examples/extensions/sandbox/index.ts b/packages/coding-agent/examples/extensions/sandbox/index.ts
index 94f8f1cf..b54d75d1 100644
--- a/packages/coding-agent/examples/extensions/sandbox/index.ts
+++ b/packages/coding-agent/examples/extensions/sandbox/index.ts
@@ -46,7 +46,7 @@ import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { SandboxManager, type SandboxRuntimeConfig } from "@anthropic-ai/sandbox-runtime";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
-import { type BashOperations, createBashTool, getAgentDir } from "@earendil-works/pi-coding-agent";
+import { type BashOperations, CONFIG_DIR_NAME, createBashTool, getAgentDir } from "@earendil-works/pi-coding-agent";
interface SandboxConfig extends SandboxRuntimeConfig {
enabled?: boolean;
@@ -77,7 +77,7 @@ const DEFAULT_CONFIG: SandboxConfig = {
};
function loadConfig(cwd: string): SandboxConfig {
- const projectConfigPath = join(cwd, ".pi", "sandbox.json");
+ const projectConfigPath = join(cwd, CONFIG_DIR_NAME, "sandbox.json");
const globalConfigPath = join(getAgentDir(), "extensions", "sandbox.json");
let globalConfig: Partial = {};
diff --git a/packages/coding-agent/examples/extensions/sandbox/package-lock.json b/packages/coding-agent/examples/extensions/sandbox/package-lock.json
index 360c0def..0558de01 100644
--- a/packages/coding-agent/examples/extensions/sandbox/package-lock.json
+++ b/packages/coding-agent/examples/extensions/sandbox/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "pi-extension-sandbox",
- "version": "1.9.1",
+ "version": "1.9.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pi-extension-sandbox",
- "version": "1.9.1",
+ "version": "1.9.10",
"dependencies": {
"@anthropic-ai/sandbox-runtime": "^0.0.26"
}
diff --git a/packages/coding-agent/examples/extensions/sandbox/package.json b/packages/coding-agent/examples/extensions/sandbox/package.json
index f285b6ce..5957ad4b 100644
--- a/packages/coding-agent/examples/extensions/sandbox/package.json
+++ b/packages/coding-agent/examples/extensions/sandbox/package.json
@@ -1,7 +1,7 @@
{
"name": "pi-extension-sandbox",
"private": true,
- "version": "1.9.1",
+ "version": "1.9.10",
"type": "module",
"scripts": {
"clean": "echo 'nothing to clean'",
diff --git a/packages/coding-agent/examples/extensions/subagent/agents.ts b/packages/coding-agent/examples/extensions/subagent/agents.ts
index eab6c630..c41ef579 100644
--- a/packages/coding-agent/examples/extensions/subagent/agents.ts
+++ b/packages/coding-agent/examples/extensions/subagent/agents.ts
@@ -4,7 +4,7 @@
import * as fs from "node:fs";
import * as path from "node:path";
-import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
+import { CONFIG_DIR_NAME, getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
export type AgentScope = "user" | "project" | "both";
@@ -85,7 +85,7 @@ function isDirectory(p: string): boolean {
function findNearestProjectAgentsDir(cwd: string): string | null {
let currentDir = cwd;
while (true) {
- const candidate = path.join(currentDir, ".pi", "agents");
+ const candidate = path.join(currentDir, CONFIG_DIR_NAME, "agents");
if (isDirectory(candidate)) return candidate;
const parentDir = path.dirname(currentDir);
diff --git a/packages/coding-agent/examples/extensions/subagent/index.ts b/packages/coding-agent/examples/extensions/subagent/index.ts
index 92b471a3..832dcc74 100644
--- a/packages/coding-agent/examples/extensions/subagent/index.ts
+++ b/packages/coding-agent/examples/extensions/subagent/index.ts
@@ -19,7 +19,13 @@ import * as path from "node:path";
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
import type { Message } from "@earendil-works/pi-ai";
import { StringEnum } from "@earendil-works/pi-ai";
-import { type ExtensionAPI, getMarkdownTheme, withFileMutationQueue } from "@earendil-works/pi-coding-agent";
+import {
+ CONFIG_DIR_NAME,
+ type ExtensionAPI,
+ getAgentDir,
+ getMarkdownTheme,
+ withFileMutationQueue,
+} from "@earendil-works/pi-coding-agent";
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
import { Type } from "typebox";
import { type AgentConfig, type AgentScope, discoverAgents } from "./agents.ts";
@@ -458,8 +464,8 @@ export default function (pi: ExtensionAPI) {
description: [
"Delegate tasks to specialized subagents with isolated context.",
"Modes: single (agent + task), parallel (tasks array), chain (sequential with {previous} placeholder).",
- 'Default agent scope is "user" (from ~/.pi/agent/agents).',
- 'To enable project-local agents in .pi/agents, set agentScope: "both" (or "project").',
+ `Default agent scope is "user" (from ${path.join(getAgentDir(), "agents")}).`,
+ `To enable project-local agents in ${CONFIG_DIR_NAME}/agents, set agentScope: "both" (or "project").`,
].join(" "),
parameters: SubagentParams,
diff --git a/packages/coding-agent/examples/extensions/with-deps/package-lock.json b/packages/coding-agent/examples/extensions/with-deps/package-lock.json
index ee75d24f..dac510c0 100644
--- a/packages/coding-agent/examples/extensions/with-deps/package-lock.json
+++ b/packages/coding-agent/examples/extensions/with-deps/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "pi-extension-with-deps",
- "version": "0.79.1",
+ "version": "0.79.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pi-extension-with-deps",
- "version": "0.79.1",
+ "version": "0.79.10",
"dependencies": {
"ms": "^2.1.3"
},
diff --git a/packages/coding-agent/examples/extensions/with-deps/package.json b/packages/coding-agent/examples/extensions/with-deps/package.json
index fab9f145..e23e480f 100644
--- a/packages/coding-agent/examples/extensions/with-deps/package.json
+++ b/packages/coding-agent/examples/extensions/with-deps/package.json
@@ -1,7 +1,7 @@
{
"name": "pi-extension-with-deps",
"private": true,
- "version": "0.79.1",
+ "version": "0.79.10",
"type": "module",
"scripts": {
"clean": "echo 'nothing to clean'",
diff --git a/packages/coding-agent/npm-shrinkwrap.json b/packages/coding-agent/npm-shrinkwrap.json
index 9132d489..1e9fa157 100644
--- a/packages/coding-agent/npm-shrinkwrap.json
+++ b/packages/coding-agent/npm-shrinkwrap.json
@@ -1,17 +1,17 @@
{
"name": "@earendil-works/pi-coding-agent",
- "version": "0.79.1",
+ "version": "0.79.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@earendil-works/pi-coding-agent",
- "version": "0.79.1",
+ "version": "0.79.10",
"license": "MIT",
"dependencies": {
- "@earendil-works/pi-agent-core": "^0.79.1",
- "@earendil-works/pi-ai": "^0.79.1",
- "@earendil-works/pi-tui": "^0.79.1",
+ "@earendil-works/pi-agent-core": "^0.79.10",
+ "@earendil-works/pi-ai": "^0.79.10",
+ "@earendil-works/pi-tui": "^0.79.10",
"@silvia-odwyer/photon-node": "0.3.4",
"chalk": "5.6.2",
"cross-spawn": "7.0.6",
@@ -23,8 +23,9 @@
"jiti": "2.7.0",
"minimatch": "10.2.5",
"proper-lockfile": "4.1.2",
+ "semver": "7.8.0",
"typebox": "1.1.38",
- "undici": "8.3.0",
+ "undici": "8.5.0",
"yaml": "2.9.0"
},
"optionalDependencies": {
@@ -473,11 +474,11 @@
}
},
"node_modules/@earendil-works/pi-agent-core": {
- "version": "0.79.1",
- "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.79.1.tgz",
+ "version": "0.79.10",
+ "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.79.10.tgz",
"license": "MIT",
"dependencies": {
- "@earendil-works/pi-ai": "^0.79.1",
+ "@earendil-works/pi-ai": "^0.79.10",
"ignore": "7.0.5",
"typebox": "1.1.38",
"yaml": "2.9.0"
@@ -487,15 +488,16 @@
}
},
"node_modules/@earendil-works/pi-ai": {
- "version": "0.79.1",
- "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.79.1.tgz",
+ "version": "0.79.10",
+ "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.79.10.tgz",
"license": "MIT",
"dependencies": {
"@anthropic-ai/sdk": "0.91.1",
"@aws-sdk/client-bedrock-runtime": "3.1048.0",
- "@smithy/node-http-handler": "4.7.3",
"@google/genai": "1.52.0",
- "@mistralai/mistralai": "2.2.1",
+ "@mistralai/mistralai": "2.2.6",
+ "@opentelemetry/api": "1.9.0",
+ "@smithy/node-http-handler": "4.7.3",
"http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.6",
"openai": "6.26.0",
@@ -510,12 +512,12 @@
}
},
"node_modules/@earendil-works/pi-tui": {
- "version": "0.79.1",
- "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.79.1.tgz",
+ "version": "0.79.10",
+ "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.79.10.tgz",
"license": "MIT",
"dependencies": {
"get-east-asian-width": "1.6.0",
- "marked": "15.0.12"
+ "marked": "18.0.5"
},
"engines": {
"node": ">=22.19.0"
@@ -740,14 +742,23 @@
"optional": true
},
"node_modules/@mistralai/mistralai": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.1.tgz",
- "integrity": "sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ==",
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.6.tgz",
+ "integrity": "sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==",
"license": "Apache-2.0",
"dependencies": {
+ "@opentelemetry/semantic-conventions": "^1.40.0",
"ws": "^8.18.0",
"zod": "^3.25.0 || ^4.0.0",
"zod-to-json-schema": "^3.25.0"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.9.0"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ }
}
},
"node_modules/@nodable/entities": {
@@ -762,6 +773,24 @@
}
]
},
+ "node_modules/@opentelemetry/api": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
+ "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/@opentelemetry/semantic-conventions": {
+ "version": "1.41.1",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz",
+ "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@@ -781,9 +810,9 @@
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/eventemitter": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
- "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
+ "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/fetch": {
@@ -801,12 +830,6 @@
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
"license": "BSD-3-Clause"
},
- "node_modules/@protobufjs/inquire": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz",
- "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==",
- "license": "BSD-3-Clause"
- },
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
@@ -1398,15 +1421,15 @@
}
},
"node_modules/marked": {
- "version": "15.0.12",
- "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz",
- "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
+ "version": "18.0.5",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz",
+ "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
- "node": ">= 18"
+ "node": ">= 20"
}
},
"node_modules/minimatch": {
@@ -1584,23 +1607,22 @@
}
},
"node_modules/protobufjs": {
- "version": "7.5.9",
- "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.9.tgz",
- "integrity": "sha512-Od4muIm3HW1AouyHF5lONOf1FWo3hY1NbFDoy191X9GzhpgW1clCoaFjfVs2rKJNFYpTNJbje4cbAIDBZJ63ZA==",
+ "version": "7.6.4",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz",
+ "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==",
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.5",
- "@protobufjs/eventemitter": "^1.1.0",
+ "@protobufjs/eventemitter": "^1.1.1",
"@protobufjs/fetch": "^1.1.1",
"@protobufjs/float": "^1.0.2",
- "@protobufjs/inquire": "^1.1.2",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.1",
"@types/node": ">=13.7.0",
- "long": "^5.0.0"
+ "long": "^5.3.2"
},
"engines": {
"node": ">=12.0.0"
@@ -1636,6 +1658,18 @@
}
]
},
+ "node_modules/semver": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
+ "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -1694,9 +1728,9 @@
"license": "MIT"
},
"node_modules/undici": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-8.3.0.tgz",
- "integrity": "sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q==",
+ "version": "8.5.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-8.5.0.tgz",
+ "integrity": "sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==",
"license": "MIT",
"engines": {
"node": ">=22.19.0"
@@ -1733,9 +1767,9 @@
}
},
"node_modules/ws": {
- "version": "8.20.1",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz",
- "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==",
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
+ "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
"license": "MIT",
"peerDependencies": {
"bufferutil": "^4.0.1",
diff --git a/packages/coding-agent/package.json b/packages/coding-agent/package.json
index b9381465..81489b28 100644
--- a/packages/coding-agent/package.json
+++ b/packages/coding-agent/package.json
@@ -1,6 +1,6 @@
{
"name": "@earendil-works/pi-coding-agent",
- "version": "0.79.1",
+ "version": "0.79.10",
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
"type": "module",
"piConfig": {
@@ -36,9 +36,9 @@
"prepublishOnly": "npm run clean && npm run build && npm run shrinkwrap"
},
"dependencies": {
- "@earendil-works/pi-agent-core": "^0.79.1",
- "@earendil-works/pi-ai": "^0.79.1",
- "@earendil-works/pi-tui": "^0.79.1",
+ "@earendil-works/pi-agent-core": "^0.79.10",
+ "@earendil-works/pi-ai": "^0.79.10",
+ "@earendil-works/pi-tui": "^0.79.10",
"@silvia-odwyer/photon-node": "0.3.4",
"chalk": "5.6.2",
"cross-spawn": "7.0.6",
@@ -50,8 +50,9 @@
"jiti": "2.7.0",
"minimatch": "10.2.5",
"proper-lockfile": "4.1.2",
+ "semver": "7.8.0",
"typebox": "1.1.38",
- "undici": "8.3.0",
+ "undici": "8.5.0",
"yaml": "2.9.0"
},
"overrides": {
@@ -70,9 +71,10 @@
"@types/ms": "2.1.0",
"@types/node": "24.12.4",
"@types/proper-lockfile": "4.1.4",
+ "@types/semver": "7.7.1",
"shx": "0.4.0",
"typescript": "5.9.3",
- "vitest": "3.2.4"
+ "vitest": "4.1.9"
},
"keywords": [
"coding-agent",
diff --git a/packages/coding-agent/src/bun/restore-sandbox-env.ts b/packages/coding-agent/src/bun/restore-sandbox-env.ts
index b175a494..445ddfd6 100644
--- a/packages/coding-agent/src/bun/restore-sandbox-env.ts
+++ b/packages/coding-agent/src/bun/restore-sandbox-env.ts
@@ -4,6 +4,10 @@
* Bun compiled binaries have an empty `process.env` when running inside
* sandbox environments (e.g. nono on Linux/macOS). On Linux we can recover
* the environment from `/proc/self/environ`.
+ *
+ * Keep this in sync with getBunSandboxEnvValue() in
+ * packages/ai/src/utils/provider-env.ts. The ai package duplicates the lookup
+ * for direct consumers that do not go through this coding-agent entrypoint.
*/
import { readFileSync } from "node:fs";
diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts
index 839c60e8..5d3c4af8 100644
--- a/packages/coding-agent/src/cli/args.ts
+++ b/packages/coding-agent/src/cli/args.ts
@@ -229,7 +229,7 @@ ${chalk.bold("Commands:")}
${APP_NAME} install [-l] Install extension source and add to settings
${APP_NAME} remove [-l] Remove extension source from settings
${APP_NAME} uninstall [-l] Alias for remove
- ${APP_NAME} update [source|self|pi] Update pi and installed extensions
+ ${APP_NAME} update [source|self|pi] Update pi (use --all for pi and extensions)
${APP_NAME} list List installed extensions from settings
${APP_NAME} config Open TUI to enable/disable package resources
${APP_NAME} --help Show help for install/remove/uninstall/update/list
diff --git a/packages/coding-agent/src/cli/startup-ui.ts b/packages/coding-agent/src/cli/startup-ui.ts
index 5a9de203..93841304 100644
--- a/packages/coding-agent/src/cli/startup-ui.ts
+++ b/packages/coding-agent/src/cli/startup-ui.ts
@@ -1,6 +1,6 @@
import { ProcessTerminal, setKeybindings, TUI } from "@earendil-works/pi-tui";
import { existsSync } from "fs";
-import { ENV_AGENT_DIR, getSettingsPath } from "../config.ts";
+import { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR, getSettingsPath, PACKAGE_NAME } from "../config.ts";
import { areExperimentalFeaturesEnabled } from "../core/experimental.ts";
import { KeybindingsManager } from "../core/keybindings.ts";
import type { SettingsManager } from "../core/settings-manager.ts";
@@ -10,7 +10,25 @@ import {
FirstTimeSetupComponent,
type FirstTimeSetupResult,
} from "../modes/interactive/components/first-time-setup.ts";
-import { detectTerminalBackground, initTheme, setTheme } from "../modes/interactive/theme/theme.ts";
+import { detectTerminalBackgroundTheme, initTheme, setTheme } from "../modes/interactive/theme/theme.ts";
+
+const OFFICIAL_PACKAGE_NAME = "@earendil-works/pi-coding-agent";
+const OFFICIAL_APP_NAME = "pi";
+const OFFICIAL_CONFIG_DIR_NAME = ".pi";
+
+interface DistributionMetadata {
+ packageName: string;
+ appName: string;
+ configDirName: string;
+}
+
+function isOfficialDistribution({ packageName, appName, configDirName }: DistributionMetadata): boolean {
+ return (
+ packageName === OFFICIAL_PACKAGE_NAME &&
+ appName === OFFICIAL_APP_NAME &&
+ configDirName === OFFICIAL_CONFIG_DIR_NAME
+ );
+}
function createStartupTui(settingsManager: SettingsManager): TUI {
initTheme(settingsManager.getTheme());
@@ -28,11 +46,21 @@ async function clearStartupTui(ui: TUI): Promise {
/**
* First-time setup runs when all of these hold:
+ * - this is the official Pi distribution (not a fork/rebrand)
* - experimental features are enabled (PI_EXPERIMENTAL=1)
* - the default agent directory is used (no custom agent dir override)
* - setup was not completed before (settings.json does not exist)
*/
export function shouldRunFirstTimeSetup(settingsPath: string = getSettingsPath()): boolean {
+ if (
+ !isOfficialDistribution({
+ packageName: PACKAGE_NAME,
+ appName: APP_NAME,
+ configDirName: CONFIG_DIR_NAME,
+ })
+ ) {
+ return false;
+ }
if (!areExperimentalFeaturesEnabled()) {
return false;
}
@@ -95,19 +123,25 @@ export async function showFirstTimeSetup(settingsManager: SettingsManager): Prom
resolve();
};
- const component = new FirstTimeSetupComponent({
- detectedTheme: detectTerminalBackground().theme,
- onThemePreview: (themeName) => {
- setTheme(themeName);
- ui.invalidate();
- ui.requestRender();
- },
- onSubmit: (result) => void finish(result),
- onCancel: () => void finish(undefined),
- });
- ui.addChild(component);
- ui.setFocus(component);
- ui.start();
+ const showSetup = async () => {
+ ui.start();
+ const detection = await detectTerminalBackgroundTheme({ ui, timeoutMs: 100 });
+ setTheme(detection.theme);
+ const component = new FirstTimeSetupComponent({
+ detectedTheme: detection.theme,
+ onThemePreview: (themeName) => {
+ setTheme(themeName);
+ ui.requestRender();
+ },
+ onSubmit: (result) => void finish(result),
+ onCancel: () => void finish(undefined),
+ });
+ ui.addChild(component);
+ ui.setFocus(component);
+ ui.requestRender();
+ };
+
+ void showSetup();
});
}
diff --git a/packages/coding-agent/src/config.ts b/packages/coding-agent/src/config.ts
index 65fb220e..38049f05 100644
--- a/packages/coding-agent/src/config.ts
+++ b/packages/coding-agent/src/config.ts
@@ -38,6 +38,18 @@ export interface SelfUpdateCommand extends SelfUpdateCommandStep {
steps?: SelfUpdateCommandStep[];
}
+export type SelfUpdatePackageTarget = string | { packageName: string; installSpec?: string };
+
+function normalizeSelfUpdatePackageTarget(target: SelfUpdatePackageTarget): {
+ packageName: string;
+ installSpec: string;
+} {
+ if (typeof target === "string") {
+ return { packageName: target, installSpec: target };
+ }
+ return { packageName: target.packageName, installSpec: target.installSpec ?? target.packageName };
+}
+
function makeSelfUpdateCommand(
installStep: SelfUpdateCommandStep,
uninstallStep?: SelfUpdateCommandStep,
@@ -103,29 +115,38 @@ function getInferredNpmInstall(): { root: string; prefix: string } | undefined {
function getSelfUpdateCommandForMethod(
method: InstallMethod,
installedPackageName: string,
- updatePackageName = installedPackageName,
+ updatePackageTarget: SelfUpdatePackageTarget = installedPackageName,
npmCommand?: string[],
): SelfUpdateCommand | undefined {
+ const target = normalizeSelfUpdatePackageTarget(updatePackageTarget);
switch (method) {
case "bun-binary":
return undefined;
- case "pnpm":
+ case "pnpm": {
+ const match = readCommandOutput("pnpm", ["root", "-g"])
+ ? undefined
+ : /^(.*[\\/]global[\\/][^\\/]+)[\\/]\.pnpm[\\/]/.exec(getPackageDir());
+ const binDirArgs = match
+ ? [`--config.global-bin-dir=${process.env.PNPM_HOME || dirname(dirname(match[1]))}`]
+ : [];
return makeSelfUpdateCommand(
makeSelfUpdateCommandStep("pnpm", [
"install",
"-g",
"--ignore-scripts",
"--config.minimumReleaseAge=0",
- updatePackageName,
+ ...binDirArgs,
+ target.installSpec,
]),
- updatePackageName === installedPackageName
+ target.packageName === installedPackageName
? undefined
- : makeSelfUpdateCommandStep("pnpm", ["remove", "-g", installedPackageName]),
+ : makeSelfUpdateCommandStep("pnpm", ["remove", "-g", ...binDirArgs, installedPackageName]),
);
+ }
case "yarn":
return makeSelfUpdateCommand(
- makeSelfUpdateCommandStep("yarn", ["global", "add", "--ignore-scripts", updatePackageName]),
- updatePackageName === installedPackageName
+ makeSelfUpdateCommandStep("yarn", ["global", "add", "--ignore-scripts", target.installSpec]),
+ target.packageName === installedPackageName
? undefined
: makeSelfUpdateCommandStep("yarn", ["global", "remove", installedPackageName]),
);
@@ -136,9 +157,9 @@ function getSelfUpdateCommandForMethod(
"-g",
"--ignore-scripts",
"--minimum-release-age=0",
- updatePackageName,
+ target.installSpec,
]),
- updatePackageName === installedPackageName
+ target.packageName === installedPackageName
? undefined
: makeSelfUpdateCommandStep("bun", ["uninstall", "-g", installedPackageName]),
);
@@ -152,10 +173,10 @@ function getSelfUpdateCommandForMethod(
"-g",
"--ignore-scripts",
"--min-release-age=0",
- updatePackageName,
+ target.installSpec,
]);
const uninstallStep =
- updatePackageName === installedPackageName
+ target.packageName === installedPackageName
? undefined
: makeSelfUpdateCommandStep(command, [...prefixArgs, "uninstall", "-g", installedPackageName]);
return makeSelfUpdateCommand(installStep, uninstallStep);
@@ -205,7 +226,9 @@ function getGlobalPackageRoots(method: InstallMethod, _packageName: string, npmC
}
case "pnpm": {
const root = readCommandOutput("pnpm", ["root", "-g"]);
- return root ? [root, dirname(root)] : [];
+ if (root) return [root, dirname(root)];
+ const match = /^(.*[\\/]global[\\/][^\\/]+)[\\/]\.pnpm[\\/]/.exec(getPackageDir());
+ return match ? [match[1]] : [];
}
case "yarn": {
const dir = readCommandOutput("yarn", ["global", "dir"]);
@@ -292,10 +315,10 @@ function isManagedByGlobalPackageManager(method: InstallMethod, packageName: str
export function getSelfUpdateCommand(
packageName: string,
npmCommand?: string[],
- updatePackageName = packageName,
+ updatePackageTarget: SelfUpdatePackageTarget = packageName,
): SelfUpdateCommand | undefined {
const method = detectInstallMethod();
- const command = getSelfUpdateCommandForMethod(method, packageName, updatePackageName, npmCommand);
+ const command = getSelfUpdateCommandForMethod(method, packageName, updatePackageTarget, npmCommand);
if (!command || !isManagedByGlobalPackageManager(method, packageName, npmCommand) || !isSelfUpdatePathWritable()) {
return undefined;
}
@@ -305,20 +328,21 @@ export function getSelfUpdateCommand(
export function getSelfUpdateUnavailableInstruction(
packageName: string,
npmCommand?: string[],
- updatePackageName = packageName,
+ updatePackageTarget: SelfUpdatePackageTarget = packageName,
): string {
const method = detectInstallMethod();
+ const target = normalizeSelfUpdatePackageTarget(updatePackageTarget);
if (method === "bun-binary") {
return `Download from: https://github.com/earendil-works/pi-mono/releases/latest`;
}
- const command = getSelfUpdateCommandForMethod(method, packageName, updatePackageName, npmCommand);
+ const command = getSelfUpdateCommandForMethod(method, packageName, target, npmCommand);
if (command) {
if (isManagedByGlobalPackageManager(method, packageName, npmCommand) && !isSelfUpdatePathWritable()) {
return `This installation is managed by a global ${method} install, but the install path is not writable. Update it yourself with: ${command.display}`;
}
return `This installation is not managed by a global ${method} install. Update it with the package manager, wrapper, or source checkout that provides it.`;
}
- return `Update ${updatePackageName} using the package manager, wrapper, or source checkout that provides this installation.`;
+ return `Update ${target.installSpec} using the package manager, wrapper, or source checkout that provides this installation.`;
}
export function getUpdateInstruction(packageName: string): string {
diff --git a/packages/coding-agent/src/core/agent-session.ts b/packages/coding-agent/src/core/agent-session.ts
index 1b5f1c7d..26022f63 100644
--- a/packages/coding-agent/src/core/agent-session.ts
+++ b/packages/coding-agent/src/core/agent-session.ts
@@ -33,7 +33,7 @@ import {
resetApiProviders,
streamSimple,
} from "@earendil-works/pi-ai/compat";
-import { theme } from "../modes/interactive/theme/theme.ts";
+import { getThemeByName, theme } from "../modes/interactive/theme/theme.ts";
import { stripFrontmatter } from "../utils/frontmatter.ts";
import { resolvePath } from "../utils/paths.ts";
import { sleep } from "../utils/sleep.ts";
@@ -45,6 +45,7 @@ import {
collectEntriesForBranchSummary,
compact,
estimateContextTokens,
+ estimateTokens,
generateBranchSummary,
prepareCompaction,
shouldCompact,
@@ -242,6 +243,14 @@ interface ToolDefinitionEntry {
sourceInfo: SourceInfo;
}
+function estimateMessagesTokens(messages: AgentMessage[]): number {
+ let tokens = 0;
+ for (const message of messages) {
+ tokens += estimateTokens(message);
+ }
+ return tokens;
+}
+
// ============================================================================
// Constants
// ============================================================================
@@ -357,6 +366,7 @@ export class AgentSession {
private async _getRequiredRequestAuth(model: Model): Promise<{
apiKey: string;
headers?: Record;
+ env?: Record;
}> {
const result = await this._modelRegistry.getApiKeyAndHeaders(model);
if (!result.ok) {
@@ -366,7 +376,7 @@ export class AgentSession {
throw new Error(result.error);
}
if (result.apiKey) {
- return { apiKey: result.apiKey, headers: result.headers };
+ return { apiKey: result.apiKey, headers: result.headers, env: result.env };
}
const isOAuth = this._modelRegistry.isUsingOAuth(model);
@@ -383,13 +393,14 @@ export class AgentSession {
private async _getCompactionRequestAuth(model: Model): Promise<{
apiKey?: string;
headers?: Record;
+ env?: Record;
}> {
if (this.agent.streamFn === streamSimple) {
return this._getRequiredRequestAuth(model);
}
const result = await this._modelRegistry.getApiKeyAndHeaders(model);
- return result.ok ? { apiKey: result.apiKey, headers: result.headers } : {};
+ return result.ok ? { apiKey: result.apiKey, headers: result.headers, env: result.env } : {};
}
/**
@@ -1649,7 +1660,7 @@ export class AgentSession {
throw new Error(formatNoModelSelectedMessage());
}
- const { apiKey, headers } = await this._getCompactionRequestAuth(this.model);
+ const { apiKey, headers, env } = await this._getCompactionRequestAuth(this.model);
const pathEntries = this.sessionManager.getBranch();
const settings = this.settingsManager.getCompactionSettings();
@@ -1673,6 +1684,8 @@ export class AgentSession {
preparation,
branchEntries: pathEntries,
customInstructions,
+ reason: "manual",
+ willRetry: false,
signal: this._compactionAbortController.signal,
})) as SessionBeforeCompactResult | undefined;
@@ -1708,6 +1721,7 @@ export class AgentSession {
this._compactionAbortController.signal,
this.thinkingLevel,
this.agent.streamFn,
+ env,
);
summary = result.summary;
firstKeptEntryId = result.firstKeptEntryId;
@@ -1723,6 +1737,7 @@ export class AgentSession {
const newEntries = this.sessionManager.getEntries();
const sessionContext = this.sessionManager.buildSessionContext();
this.agent.state.messages = sessionContext.messages;
+ const estimatedTokensAfter = estimateMessagesTokens(sessionContext.messages);
// Get the saved compaction entry for the extension event
const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary) as
@@ -1734,13 +1749,16 @@ export class AgentSession {
type: "session_compact",
compactionEntry: savedCompactionEntry,
fromExtension,
+ reason: "manual",
+ willRetry: false,
});
}
- const compactionResult = {
+ const compactionResult: CompactionResult = {
summary,
firstKeptEntryId,
tokensBefore,
+ estimatedTokensAfter,
details,
};
this._emit({
@@ -1821,8 +1839,17 @@ export class AgentSession {
return false;
}
- // Case 1: Overflow - LLM returned context overflow error
+ // Case 1: Overflow - LLM returned context overflow error, or reported usage exceeded
+ // the configured window. A successful response over the configured window should compact
+ // but must not retry: the assistant answer already completed and agent.continue() cannot
+ // continue from an assistant message.
if (sameModel && isContextOverflow(assistantMessage, contextWindow)) {
+ const willRetry = assistantMessage.stopReason !== "stop";
+
+ if (!willRetry) {
+ return await this._runAutoCompaction("overflow", false);
+ }
+
if (this._overflowRecoveryAttempted) {
this._emit({
type: "compaction_end",
@@ -1843,7 +1870,7 @@ export class AgentSession {
if (messages.length > 0 && messages[messages.length - 1].role === "assistant") {
this.agent.state.messages = messages.slice(0, -1);
}
- return await this._runAutoCompaction("overflow", true);
+ return await this._runAutoCompaction("overflow", willRetry);
}
// Case 2: Threshold - context is getting large
@@ -1880,56 +1907,39 @@ export class AgentSession {
*/
private async _runAutoCompaction(reason: "overflow" | "threshold", willRetry: boolean): Promise {
const settings = this.settingsManager.getCompactionSettings();
-
- this._emit({ type: "compaction_start", reason });
- this._autoCompactionAbortController = new AbortController();
+ let started = false;
try {
if (!this.model) {
- this._emit({
- type: "compaction_end",
- reason,
- result: undefined,
- aborted: false,
- willRetry: false,
- });
return false;
}
let apiKey: string | undefined;
let headers: Record | undefined;
+ let env: Record | undefined;
if (this.agent.streamFn === streamSimple) {
const authResult = await this._modelRegistry.getApiKeyAndHeaders(this.model);
if (!authResult.ok || !authResult.apiKey) {
- this._emit({
- type: "compaction_end",
- reason,
- result: undefined,
- aborted: false,
- willRetry: false,
- });
return false;
}
apiKey = authResult.apiKey;
headers = authResult.headers;
+ env = authResult.env;
} else {
- ({ apiKey, headers } = await this._getCompactionRequestAuth(this.model));
+ ({ apiKey, headers, env } = await this._getCompactionRequestAuth(this.model));
}
const pathEntries = this.sessionManager.getBranch();
const preparation = prepareCompaction(pathEntries, settings);
if (!preparation) {
- this._emit({
- type: "compaction_end",
- reason,
- result: undefined,
- aborted: false,
- willRetry: false,
- });
return false;
}
+ this._emit({ type: "compaction_start", reason });
+ this._autoCompactionAbortController = new AbortController();
+ started = true;
+
let extensionCompaction: CompactionResult | undefined;
let fromExtension = false;
@@ -1939,6 +1949,8 @@ export class AgentSession {
preparation,
branchEntries: pathEntries,
customInstructions: undefined,
+ reason,
+ willRetry,
signal: this._autoCompactionAbortController.signal,
})) as SessionBeforeCompactResult | undefined;
@@ -1981,6 +1993,7 @@ export class AgentSession {
this._autoCompactionAbortController.signal,
this.thinkingLevel,
this.agent.streamFn,
+ env,
);
summary = compactResult.summary;
firstKeptEntryId = compactResult.firstKeptEntryId;
@@ -2003,6 +2016,7 @@ export class AgentSession {
const newEntries = this.sessionManager.getEntries();
const sessionContext = this.sessionManager.buildSessionContext();
this.agent.state.messages = sessionContext.messages;
+ const estimatedTokensAfter = estimateMessagesTokens(sessionContext.messages);
// Get the saved compaction entry for the extension event
const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary) as
@@ -2014,6 +2028,8 @@ export class AgentSession {
type: "session_compact",
compactionEntry: savedCompactionEntry,
fromExtension,
+ reason,
+ willRetry,
});
}
@@ -2021,6 +2037,7 @@ export class AgentSession {
summary,
firstKeptEntryId,
tokensBefore,
+ estimatedTokensAfter,
details,
};
this._emit({ type: "compaction_end", reason, result, aborted: false, willRetry });
@@ -2039,17 +2056,19 @@ export class AgentSession {
return this.agent.hasQueuedMessages();
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "compaction failed";
- this._emit({
- type: "compaction_end",
- reason,
- result: undefined,
- aborted: false,
- willRetry: false,
- errorMessage:
- reason === "overflow"
- ? `Context overflow recovery failed: ${errorMessage}`
- : `Auto-compaction failed: ${errorMessage}`,
- });
+ if (started) {
+ this._emit({
+ type: "compaction_end",
+ reason,
+ result: undefined,
+ aborted: false,
+ willRetry: false,
+ errorMessage:
+ reason === "overflow"
+ ? `Context overflow recovery failed: ${errorMessage}`
+ : `Auto-compaction failed: ${errorMessage}`,
+ });
+ }
return false;
} finally {
this._autoCompactionAbortController = undefined;
@@ -2432,7 +2451,7 @@ export class AgentSession {
});
}
- async reload(): Promise {
+ async reload(options?: { beforeSessionStart?: () => void | Promise }): Promise {
const previousFlagValues = this._extensionRunner.getFlagValues();
await emitSessionShutdownEvent(this._extensionRunner, { type: "session_shutdown", reason: "reload" });
await this.settingsManager.reload();
@@ -2451,6 +2470,7 @@ export class AgentSession {
this._extensionShutdownHandler ||
this._extensionErrorListener;
if (hasBindings) {
+ await options?.beforeSessionStart?.();
await this._extensionRunner.emit({ type: "session_start", reason: "reload" });
await this.extendResourcesFromExtensions("reload");
}
@@ -2784,12 +2804,13 @@ export class AgentSession {
let summaryDetails: unknown;
if (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {
const model = this.model!;
- const { apiKey, headers } = await this._getRequiredRequestAuth(model);
+ const { apiKey, headers, env } = await this._getRequiredRequestAuth(model);
const branchSummarySettings = this.settingsManager.getBranchSummarySettings();
const result = await generateBranchSummary(entriesToSummarize, {
model,
apiKey,
headers,
+ env,
signal: this._branchSummaryAbortController.signal,
customInstructions,
replaceInstructions,
@@ -3017,7 +3038,8 @@ export class AgentSession {
* @returns Path to exported file
*/
async exportToHtml(outputPath?: string): Promise {
- const themeName = this.settingsManager.getTheme();
+ const configuredThemeName = this.settingsManager.getTheme();
+ const themeName = configuredThemeName && getThemeByName(configuredThemeName) ? configuredThemeName : undefined;
// Create tool renderer if we have an extension runner (for custom tool HTML rendering)
const toolRenderer: ToolHtmlRenderer = createToolHtmlRenderer({
diff --git a/packages/coding-agent/src/core/auth-storage.ts b/packages/coding-agent/src/core/auth-storage.ts
index b30d7832..31a0ec9c 100644
--- a/packages/coding-agent/src/core/auth-storage.ts
+++ b/packages/coding-agent/src/core/auth-storage.ts
@@ -24,6 +24,7 @@ import { resolveConfigValue } from "./resolve-config-value.ts";
export type ApiKeyCredential = {
type: "api_key";
key: string;
+ env?: Record;
};
export type OAuthCredential = {
@@ -40,6 +41,10 @@ export type AuthStatus = {
label?: string;
};
+export interface GetApiKeyOptions {
+ includeFallback?: boolean;
+}
+
type LockResult = {
result: T;
next?: string;
@@ -294,6 +299,14 @@ export class AuthStorage {
return this.data[provider] ?? undefined;
}
+ /**
+ * Get provider-scoped environment values for an API key credential.
+ */
+ getProviderEnv(provider: string): Record | undefined {
+ const cred = this.data[provider];
+ return cred?.type === "api_key" && cred.env ? { ...cred.env } : undefined;
+ }
+
/**
* Set credential for a provider.
*/
@@ -446,7 +459,7 @@ export class AuthStorage {
* 3. OAuth token from auth.json (auto-refreshed with locking)
* 4. Environment variable
*/
- async getApiKey(providerId: string): Promise {
+ async getApiKey(providerId: string, options: GetApiKeyOptions = {}): Promise {
// Runtime override takes highest priority
const runtimeKey = this.runtimeOverrides.get(providerId);
if (runtimeKey) {
@@ -456,7 +469,7 @@ export class AuthStorage {
const cred = this.data[providerId];
if (cred?.type === "api_key") {
- return resolveConfigValue(cred.key);
+ return resolveConfigValue(cred.key, cred.env);
}
if (cred?.type === "oauth") {
@@ -497,6 +510,8 @@ export class AuthStorage {
}
}
+ if (options.includeFallback === false) return undefined;
+
// Fall back to environment variable
const envKey = getEnvApiKey(providerId);
if (envKey) return envKey;
diff --git a/packages/coding-agent/src/core/compaction/branch-summarization.ts b/packages/coding-agent/src/core/compaction/branch-summarization.ts
index caa05bb3..3f557c01 100644
--- a/packages/coding-agent/src/core/compaction/branch-summarization.ts
+++ b/packages/coding-agent/src/core/compaction/branch-summarization.ts
@@ -69,6 +69,8 @@ export interface GenerateBranchSummaryOptions {
apiKey: string;
/** Request headers for the model */
headers?: Record;
+ /** Provider-scoped environment values for the model */
+ env?: Record;
/** Abort signal for cancellation */
signal: AbortSignal;
/** Optional custom instructions for summarization */
@@ -290,6 +292,7 @@ export async function generateBranchSummary(
model,
apiKey,
headers,
+ env,
signal,
customInstructions,
replaceInstructions,
@@ -335,7 +338,7 @@ export async function generateBranchSummary(
// request behavior (timeouts, retries, attribution headers) stays consistent
// without running through agent state/events.
const context = { systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages };
- const requestOptions: SimpleStreamOptions = { apiKey, headers, signal, maxTokens: 2048 };
+ const requestOptions: SimpleStreamOptions = { apiKey, headers, env, signal, maxTokens: 2048 };
const response = streamFn
? await (await streamFn(model, context, requestOptions)).result()
: await completeSimple(model, context, requestOptions);
diff --git a/packages/coding-agent/src/core/compaction/compaction.ts b/packages/coding-agent/src/core/compaction/compaction.ts
index 43ee79ef..83b0db57 100644
--- a/packages/coding-agent/src/core/compaction/compaction.ts
+++ b/packages/coding-agent/src/core/compaction/compaction.ts
@@ -104,6 +104,7 @@ export interface CompactionResult {
summary: string;
firstKeptEntryId: string;
tokensBefore: number;
+ estimatedTokensAfter?: number;
/** Extension-specific data (e.g., ArtifactIndex, version markers for structured compaction) */
details?: T;
}
@@ -528,10 +529,11 @@ function createSummarizationOptions(
maxTokens: number,
apiKey: string | undefined,
headers: Record | undefined,
+ env: Record | undefined,
signal: AbortSignal | undefined,
thinkingLevel: ThinkingLevel | undefined,
): SimpleStreamOptions {
- const options: SimpleStreamOptions = { maxTokens, signal, apiKey, headers };
+ const options: SimpleStreamOptions = { maxTokens, signal, apiKey, headers, env };
if (model.reasoning && thinkingLevel && thinkingLevel !== "off") {
options.reasoning = thinkingLevel;
}
@@ -566,6 +568,7 @@ export async function generateSummary(
previousSummary?: string,
thinkingLevel?: ThinkingLevel,
streamFn?: StreamFn,
+ env?: Record,
): Promise {
const maxTokens = Math.min(
Math.floor(0.8 * reserveTokens),
@@ -598,7 +601,7 @@ export async function generateSummary(
},
];
- const completionOptions = createSummarizationOptions(model, maxTokens, apiKey, headers, signal, thinkingLevel);
+ const completionOptions = createSummarizationOptions(model, maxTokens, apiKey, headers, env, signal, thinkingLevel);
const response = await completeSummarization(
model,
@@ -696,6 +699,10 @@ export function prepareCompaction(
}
}
+ if (messagesToSummarize.length === 0 && turnPrefixMessages.length === 0) {
+ return undefined;
+ }
+
// Extract file operations from messages and previous compaction
const fileOps = extractFileOperations(messagesToSummarize, pathEntries, prevCompactionIndex);
@@ -753,6 +760,7 @@ export async function compact(
signal?: AbortSignal,
thinkingLevel?: ThinkingLevel,
streamFn?: StreamFn,
+ env?: Record,
): Promise {
const {
firstKeptEntryId,
@@ -783,6 +791,7 @@ export async function compact(
previousSummary,
thinkingLevel,
streamFn,
+ env,
)
: Promise.resolve("No prior history."),
generateTurnPrefixSummary(
@@ -791,6 +800,7 @@ export async function compact(
settings.reserveTokens,
apiKey,
headers,
+ env,
signal,
thinkingLevel,
streamFn,
@@ -811,6 +821,7 @@ export async function compact(
previousSummary,
thinkingLevel,
streamFn,
+ env,
);
}
@@ -839,6 +850,7 @@ async function generateTurnPrefixSummary(
reserveTokens: number,
apiKey: string | undefined,
headers?: Record,
+ env?: Record,
signal?: AbortSignal,
thinkingLevel?: ThinkingLevel,
streamFn?: StreamFn,
@@ -861,7 +873,7 @@ async function generateTurnPrefixSummary(
const response = await completeSummarization(
model,
{ systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages },
- createSummarizationOptions(model, maxTokens, apiKey, headers, signal, thinkingLevel),
+ createSummarizationOptions(model, maxTokens, apiKey, headers, env, signal, thinkingLevel),
streamFn,
);
diff --git a/packages/coding-agent/src/core/export-html/vendor/marked.min.js b/packages/coding-agent/src/core/export-html/vendor/marked.min.js
index 79394fd8..9d79575e 100644
--- a/packages/coding-agent/src/core/export-html/vendor/marked.min.js
+++ b/packages/coding-agent/src/core/export-html/vendor/marked.min.js
@@ -1,6 +1,78 @@
/**
- * marked v15.0.4 - a markdown parser
- * Copyright (c) 2011-2024, Christopher Jeffrey. (MIT Licensed)
+ * marked v18.0.5 - a markdown parser
+ * Copyright (c) 2018-2026, MarkedJS. (MIT License)
+ * Copyright (c) 2011-2018, Christopher Jeffrey. (MIT License)
* https://github.com/markedjs/marked
*/
-!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).marked={})}(this,(function(e){"use strict";function t(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}function n(t){e.defaults=t}e.defaults={async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null};const s={exec:()=>null};function r(e,t=""){let n="string"==typeof e?e:e.source;const s={replace:(e,t)=>{let r="string"==typeof t?t:t.source;return r=r.replace(i.caret,"$1"),n=n.replace(e,r),s},getRegex:()=>new RegExp(n,t)};return s}const i={codeRemoveIndent:/^(?: {1,4}| {0,3}\t)/gm,outputLinkReplace:/\\([\[\]])/g,indentCodeCompensation:/^(\s+)(?:```)/,beginningSpace:/^\s+/,endingHash:/#$/,startingSpaceChar:/^ /,endingSpaceChar:/ $/,nonSpaceChar:/[^ ]/,newLineCharGlobal:/\n/g,tabCharGlobal:/\t/g,multipleSpaceGlobal:/\s+/g,blankLine:/^[ \t]*$/,doubleBlankLine:/\n[ \t]*\n[ \t]*$/,blockquoteStart:/^ {0,3}>/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceTabs:/^\t+/,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] /,listReplaceTask:/^\[[ xX]\] +/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^,endAngleBracket:/>$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,unescapeTest:/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:e=>new RegExp(`^( {0,3}${e})((?:[\t ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`),hrRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),fencesBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}(?:\`\`\`|~~~)`),headingBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}#`),htmlBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}<(?:[a-z].*>|!--)`,"i")},l=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,o=/(?:[*+-]|\d{1,9}[.)])/,a=r(/^(?!bull |blockCode|fences|blockquote|heading|html)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html))+?)\n {0,3}(=+|-+) *(?:\n+|$)/).replace(/bull/g,o).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).getRegex(),c=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,h=/(?!\s*\])(?:\\.|[^\[\]\\])+/,p=r(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",h).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),u=r(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,o).getRegex(),g="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",k=/|$))/,f=r("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)|(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$))","i").replace("comment",k).replace("tag",g).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),d=r(c).replace("hr",l).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",g).getRegex(),x={blockquote:r(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",d).getRegex(),code:/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,def:p,fences:/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,heading:/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,hr:l,html:f,lheading:a,list:u,newline:/^(?:[ \t]*(?:\n|$))+/,paragraph:d,table:s,text:/^[^\n]+/},b=r("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",l).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code","(?: {4}| {0,3}\t)[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",g).getRegex(),w={...x,table:b,paragraph:r(c).replace("hr",l).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",b).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",g).getRegex()},m={...x,html:r("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?\\1> *(?:\\n{2,}|\\s*$)| \\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",k).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:s,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:r(c).replace("hr",l).replace("heading"," *#{1,6} *[^\n]").replace("lheading",a).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},y=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,$=/^( {2,}|\\)\n(?!\s*$)/,R=/[\p{P}\p{S}]/u,S=/[\s\p{P}\p{S}]/u,T=/[^\s\p{P}\p{S}]/u,z=r(/^((?![*_])punctSpace)/,"u").replace(/punctSpace/g,S).getRegex(),A=r(/^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/,"u").replace(/punct/g,R).getRegex(),_=r("^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)","gu").replace(/notPunctSpace/g,T).replace(/punctSpace/g,S).replace(/punct/g,R).getRegex(),P=r("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)","gu").replace(/notPunctSpace/g,T).replace(/punctSpace/g,S).replace(/punct/g,R).getRegex(),I=r(/\\(punct)/,"gu").replace(/punct/g,R).getRegex(),L=r(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),B=r(k).replace("(?:--\x3e|$)","--\x3e").getRegex(),C=r("^comment|^[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^").replace("comment",B).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),E=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,q=r(/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/).replace("label",E).replace("href",/<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),Z=r(/^!?\[(label)\]\[(ref)\]/).replace("label",E).replace("ref",h).getRegex(),v=r(/^!?\[(ref)\](?:\[\])?/).replace("ref",h).getRegex(),D={_backpedal:s,anyPunctuation:I,autolink:L,blockSkip:/\[[^[\]]*?\]\((?:\\.|[^\\\(\)]|\((?:\\.|[^\\\(\)])*\))*\)|`[^`]*?`|<[^<>]*?>/g,br:$,code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,del:s,emStrongLDelim:A,emStrongRDelimAst:_,emStrongRDelimUnd:P,escape:y,link:q,nolink:v,punctuation:z,reflink:Z,reflinkSearch:r("reflink|nolink(?!\\()","g").replace("reflink",Z).replace("nolink",v).getRegex(),tag:C,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\":">",'"':""","'":"'"},H=e=>G[e];function X(e,t){if(t){if(i.escapeTest.test(e))return e.replace(i.escapeReplace,H)}else if(i.escapeTestNoEncode.test(e))return e.replace(i.escapeReplaceNoEncode,H);return e}function F(e){try{e=encodeURI(e).replace(i.percentDecode,"%")}catch{return null}return e}function U(e,t){const n=e.replace(i.findPipe,((e,t,n)=>{let s=!1,r=t;for(;--r>=0&&"\\"===n[r];)s=!s;return s?"|":" |"})).split(i.splitPipe);let s=0;if(n[0].trim()||n.shift(),n.length>0&&!n.at(-1)?.trim()&&n.pop(),t)if(n.length>t)n.splice(t);else for(;n.length0)return{type:"space",raw:t[0]}}code(e){const t=this.rules.block.code.exec(e);if(t){const e=t[0].replace(this.rules.other.codeRemoveIndent,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?e:J(e,"\n")}}}fences(e){const t=this.rules.block.fences.exec(e);if(t){const e=t[0],n=function(e,t,n){const s=e.match(n.other.indentCodeCompensation);if(null===s)return t;const r=s[1];return t.split("\n").map((e=>{const t=e.match(n.other.beginningSpace);if(null===t)return e;const[s]=t;return s.length>=r.length?e.slice(r.length):e})).join("\n")}(e,t[3]||"",this.rules);return{type:"code",raw:e,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:n}}}heading(e){const t=this.rules.block.heading.exec(e);if(t){let e=t[2].trim();if(this.rules.other.endingHash.test(e)){const t=J(e,"#");this.options.pedantic?e=t.trim():t&&!this.rules.other.endingSpaceChar.test(t)||(e=t.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:e,tokens:this.lexer.inline(e)}}}hr(e){const t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:J(t[0],"\n")}}blockquote(e){const t=this.rules.block.blockquote.exec(e);if(t){let e=J(t[0],"\n").split("\n"),n="",s="";const r=[];for(;e.length>0;){let t=!1;const i=[];let l;for(l=0;l1,r={type:"list",raw:"",ordered:s,start:s?+n.slice(0,-1):"",loose:!1,items:[]};n=s?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=s?n:"[*+-]");const i=this.rules.other.listItemRegex(n);let l=!1;for(;e;){let n=!1,s="",o="";if(!(t=i.exec(e)))break;if(this.rules.block.hr.test(e))break;s=t[0],e=e.substring(s.length);let a=t[2].split("\n",1)[0].replace(this.rules.other.listReplaceTabs,(e=>" ".repeat(3*e.length))),c=e.split("\n",1)[0],h=!a.trim(),p=0;if(this.options.pedantic?(p=2,o=a.trimStart()):h?p=t[1].length+1:(p=t[2].search(this.rules.other.nonSpaceChar),p=p>4?1:p,o=a.slice(p),p+=t[1].length),h&&this.rules.other.blankLine.test(c)&&(s+=c+"\n",e=e.substring(c.length+1),n=!0),!n){const t=this.rules.other.nextBulletRegex(p),n=this.rules.other.hrRegex(p),r=this.rules.other.fencesBeginRegex(p),i=this.rules.other.headingBeginRegex(p),l=this.rules.other.htmlBeginRegex(p);for(;e;){const u=e.split("\n",1)[0];let g;if(c=u,this.options.pedantic?(c=c.replace(this.rules.other.listReplaceNesting," "),g=c):g=c.replace(this.rules.other.tabCharGlobal," "),r.test(c))break;if(i.test(c))break;if(l.test(c))break;if(t.test(c))break;if(n.test(c))break;if(g.search(this.rules.other.nonSpaceChar)>=p||!c.trim())o+="\n"+g.slice(p);else{if(h)break;if(a.replace(this.rules.other.tabCharGlobal," ").search(this.rules.other.nonSpaceChar)>=4)break;if(r.test(a))break;if(i.test(a))break;if(n.test(a))break;o+="\n"+c}h||c.trim()||(h=!0),s+=u+"\n",e=e.substring(u.length+1),a=g.slice(p)}}r.loose||(l?r.loose=!0:this.rules.other.doubleBlankLine.test(s)&&(l=!0));let u,g=null;this.options.gfm&&(g=this.rules.other.listIsTask.exec(o),g&&(u="[ ] "!==g[0],o=o.replace(this.rules.other.listReplaceTask,""))),r.items.push({type:"list_item",raw:s,task:!!g,checked:u,loose:!1,text:o,tokens:[]}),r.raw+=s}const o=r.items.at(-1);if(!o)return;o.raw=o.raw.trimEnd(),o.text=o.text.trimEnd(),r.raw=r.raw.trimEnd();for(let e=0;e"space"===e.type)),n=t.length>0&&t.some((e=>this.rules.other.anyLine.test(e.raw)));r.loose=n}if(r.loose)for(let e=0;e({text:e,tokens:this.lexer.inline(e),header:!1,align:i.align[t]}))));return i}}lheading(e){const t=this.rules.block.lheading.exec(e);if(t)return{type:"heading",raw:t[0],depth:"="===t[2].charAt(0)?1:2,text:t[1],tokens:this.lexer.inline(t[1])}}paragraph(e){const t=this.rules.block.paragraph.exec(e);if(t){const e="\n"===t[1].charAt(t[1].length-1)?t[1].slice(0,-1):t[1];return{type:"paragraph",raw:t[0],text:e,tokens:this.lexer.inline(e)}}}text(e){const t=this.rules.block.text.exec(e);if(t)return{type:"text",raw:t[0],text:t[0],tokens:this.lexer.inline(t[0])}}escape(e){const t=this.rules.inline.escape.exec(e);if(t)return{type:"escape",raw:t[0],text:t[1]}}tag(e){const t=this.rules.inline.tag.exec(e);if(t)return!this.lexer.state.inLink&&this.rules.other.startATag.test(t[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&this.rules.other.endATag.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&this.rules.other.startPreScriptTag.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&this.rules.other.endPreScriptTag.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:t[0]}}link(e){const t=this.rules.inline.link.exec(e);if(t){const e=t[2].trim();if(!this.options.pedantic&&this.rules.other.startAngleBracket.test(e)){if(!this.rules.other.endAngleBracket.test(e))return;const t=J(e.slice(0,-1),"\\");if((e.length-t.length)%2==0)return}else{const e=function(e,t){if(-1===e.indexOf(t[1]))return-1;let n=0;for(let s=0;s-1){const n=(0===t[0].indexOf("!")?5:4)+t[1].length+e;t[2]=t[2].substring(0,e),t[0]=t[0].substring(0,n).trim(),t[3]=""}}let n=t[2],s="";if(this.options.pedantic){const e=this.rules.other.pedanticHrefTitle.exec(n);e&&(n=e[1],s=e[3])}else s=t[3]?t[3].slice(1,-1):"";return n=n.trim(),this.rules.other.startAngleBracket.test(n)&&(n=this.options.pedantic&&!this.rules.other.endAngleBracket.test(e)?n.slice(1):n.slice(1,-1)),K(t,{href:n?n.replace(this.rules.inline.anyPunctuation,"$1"):n,title:s?s.replace(this.rules.inline.anyPunctuation,"$1"):s},t[0],this.lexer,this.rules)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){const e=t[(n[2]||n[1]).replace(this.rules.other.multipleSpaceGlobal," ").toLowerCase()];if(!e){const e=n[0].charAt(0);return{type:"text",raw:e,text:e}}return K(n,e,n[0],this.lexer,this.rules)}}emStrong(e,t,n=""){let s=this.rules.inline.emStrongLDelim.exec(e);if(!s)return;if(s[3]&&n.match(this.rules.other.unicodeAlphaNumeric))return;if(!(s[1]||s[2]||"")||!n||this.rules.inline.punctuation.exec(n)){const n=[...s[0]].length-1;let r,i,l=n,o=0;const a="*"===s[0][0]?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(a.lastIndex=0,t=t.slice(-1*e.length+n);null!=(s=a.exec(t));){if(r=s[1]||s[2]||s[3]||s[4]||s[5]||s[6],!r)continue;if(i=[...r].length,s[3]||s[4]){l+=i;continue}if((s[5]||s[6])&&n%3&&!((n+i)%3)){o+=i;continue}if(l-=i,l>0)continue;i=Math.min(i,i+l+o);const t=[...s[0]][0].length,a=e.slice(0,n+s.index+t+i);if(Math.min(n,i)%2){const e=a.slice(1,-1);return{type:"em",raw:a,text:e,tokens:this.lexer.inlineTokens(e)}}const c=a.slice(2,-2);return{type:"strong",raw:a,text:c,tokens:this.lexer.inlineTokens(c)}}}}codespan(e){const t=this.rules.inline.code.exec(e);if(t){let e=t[2].replace(this.rules.other.newLineCharGlobal," ");const n=this.rules.other.nonSpaceChar.test(e),s=this.rules.other.startingSpaceChar.test(e)&&this.rules.other.endingSpaceChar.test(e);return n&&s&&(e=e.substring(1,e.length-1)),{type:"codespan",raw:t[0],text:e}}}br(e){const t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e){const t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[2],tokens:this.lexer.inlineTokens(t[2])}}autolink(e){const t=this.rules.inline.autolink.exec(e);if(t){let e,n;return"@"===t[2]?(e=t[1],n="mailto:"+e):(e=t[1],n=e),{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let e,n;if("@"===t[2])e=t[0],n="mailto:"+e;else{let s;do{s=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])?.[0]??""}while(s!==t[0]);e=t[0],n="www."===t[1]?"http://"+t[0]:t[0]}return{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}inlineText(e){const t=this.rules.inline.text.exec(e);if(t){const e=this.lexer.state.inRawBlock;return{type:"text",raw:t[0],text:t[0],escaped:e}}}}class W{tokens;options;state;tokenizer;inlineQueue;constructor(t){this.tokens=[],this.tokens.links=Object.create(null),this.options=t||e.defaults,this.options.tokenizer=this.options.tokenizer||new V,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};const n={other:i,block:j.normal,inline:N.normal};this.options.pedantic?(n.block=j.pedantic,n.inline=N.pedantic):this.options.gfm&&(n.block=j.gfm,this.options.breaks?n.inline=N.breaks:n.inline=N.gfm),this.tokenizer.rules=n}static get rules(){return{block:j,inline:N}}static lex(e,t){return new W(t).lex(e)}static lexInline(e,t){return new W(t).inlineTokens(e)}lex(e){e=e.replace(i.carriageReturn,"\n"),this.blockTokens(e,this.tokens);for(let e=0;e!!(s=n.call({lexer:this},e,t))&&(e=e.substring(s.raw.length),t.push(s),!0))))continue;if(s=this.tokenizer.space(e)){e=e.substring(s.raw.length);const n=t.at(-1);1===s.raw.length&&void 0!==n?n.raw+="\n":t.push(s);continue}if(s=this.tokenizer.code(e)){e=e.substring(s.raw.length);const n=t.at(-1);"paragraph"===n?.type||"text"===n?.type?(n.raw+="\n"+s.raw,n.text+="\n"+s.text,this.inlineQueue.at(-1).src=n.text):t.push(s);continue}if(s=this.tokenizer.fences(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.heading(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.hr(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.blockquote(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.list(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.html(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.def(e)){e=e.substring(s.raw.length);const n=t.at(-1);"paragraph"===n?.type||"text"===n?.type?(n.raw+="\n"+s.raw,n.text+="\n"+s.raw,this.inlineQueue.at(-1).src=n.text):this.tokens.links[s.tag]||(this.tokens.links[s.tag]={href:s.href,title:s.title});continue}if(s=this.tokenizer.table(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.lheading(e)){e=e.substring(s.raw.length),t.push(s);continue}let r=e;if(this.options.extensions?.startBlock){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startBlock.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(r=e.substring(0,t+1))}if(this.state.top&&(s=this.tokenizer.paragraph(r))){const i=t.at(-1);n&&"paragraph"===i?.type?(i.raw+="\n"+s.raw,i.text+="\n"+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=i.text):t.push(s),n=r.length!==e.length,e=e.substring(s.raw.length)}else if(s=this.tokenizer.text(e)){e=e.substring(s.raw.length);const n=t.at(-1);"text"===n?.type?(n.raw+="\n"+s.raw,n.text+="\n"+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=n.text):t.push(s)}else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}return this.state.top=!0,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){let n=e,s=null;if(this.tokens.links){const e=Object.keys(this.tokens.links);if(e.length>0)for(;null!=(s=this.tokenizer.rules.inline.reflinkSearch.exec(n));)e.includes(s[0].slice(s[0].lastIndexOf("[")+1,-1))&&(n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;null!=(s=this.tokenizer.rules.inline.blockSkip.exec(n));)n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);for(;null!=(s=this.tokenizer.rules.inline.anyPunctuation.exec(n));)n=n.slice(0,s.index)+"++"+n.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);let r=!1,i="";for(;e;){let s;if(r||(i=""),r=!1,this.options.extensions?.inline?.some((n=>!!(s=n.call({lexer:this},e,t))&&(e=e.substring(s.raw.length),t.push(s),!0))))continue;if(s=this.tokenizer.escape(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.tag(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.link(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.reflink(e,this.tokens.links)){e=e.substring(s.raw.length);const n=t.at(-1);"text"===s.type&&"text"===n?.type?(n.raw+=s.raw,n.text+=s.text):t.push(s);continue}if(s=this.tokenizer.emStrong(e,n,i)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.codespan(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.br(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.del(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.autolink(e)){e=e.substring(s.raw.length),t.push(s);continue}if(!this.state.inLink&&(s=this.tokenizer.url(e))){e=e.substring(s.raw.length),t.push(s);continue}let l=e;if(this.options.extensions?.startInline){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startInline.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(l=e.substring(0,t+1))}if(s=this.tokenizer.inlineText(l)){e=e.substring(s.raw.length),"_"!==s.raw.slice(-1)&&(i=s.raw.slice(-1)),r=!0;const n=t.at(-1);"text"===n?.type?(n.raw+=s.raw,n.text+=s.text):t.push(s)}else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}return t}}class Y{options;parser;constructor(t){this.options=t||e.defaults}space(e){return""}code({text:e,lang:t,escaped:n}){const s=(t||"").match(i.notSpaceStart)?.[0],r=e.replace(i.endingNewline,"")+"\n";return s?''+(n?r:X(r,!0))+" \n":""+(n?r:X(r,!0))+" \n"}blockquote({tokens:e}){return`\n${this.parser.parse(e)} \n`}html({text:e}){return e}heading({tokens:e,depth:t}){return`${this.parser.parseInline(e)} \n`}hr(e){return" \n"}list(e){const t=e.ordered,n=e.start;let s="";for(let t=0;t\n"+s+""+r+">\n"}listitem(e){let t="";if(e.task){const n=this.checkbox({checked:!!e.checked});e.loose?"paragraph"===e.tokens[0]?.type?(e.tokens[0].text=n+" "+e.tokens[0].text,e.tokens[0].tokens&&e.tokens[0].tokens.length>0&&"text"===e.tokens[0].tokens[0].type&&(e.tokens[0].tokens[0].text=n+" "+X(e.tokens[0].tokens[0].text),e.tokens[0].tokens[0].escaped=!0)):e.tokens.unshift({type:"text",raw:n+" ",text:n+" ",escaped:!0}):t+=n+" "}return t+=this.parser.parse(e.tokens,!!e.loose),`${t} \n`}checkbox({checked:e}){return" '}paragraph({tokens:e}){return`${this.parser.parseInline(e)}
\n`}table(e){let t="",n="";for(let t=0;t${s}`),"\n"}tablerow({text:e}){return`\n${e} \n`}tablecell(e){const t=this.parser.parseInline(e.tokens),n=e.header?"th":"td";return(e.align?`<${n} align="${e.align}">`:`<${n}>`)+t+`${n}>\n`}strong({tokens:e}){return`${this.parser.parseInline(e)} `}em({tokens:e}){return`${this.parser.parseInline(e)} `}codespan({text:e}){return`${X(e,!0)}`}br(e){return" "}del({tokens:e}){return`${this.parser.parseInline(e)}`}link({href:e,title:t,tokens:n}){const s=this.parser.parseInline(n),r=F(e);if(null===r)return s;let i='"+s+" ",i}image({href:e,title:t,text:n}){const s=F(e);if(null===s)return X(n);let r=` ",r}text(e){return"tokens"in e&&e.tokens?this.parser.parseInline(e.tokens):"escaped"in e&&e.escaped?e.text:X(e.text)}}class ee{strong({text:e}){return e}em({text:e}){return e}codespan({text:e}){return e}del({text:e}){return e}html({text:e}){return e}text({text:e}){return e}link({text:e}){return""+e}image({text:e}){return""+e}br(){return""}}class te{options;renderer;textRenderer;constructor(t){this.options=t||e.defaults,this.options.renderer=this.options.renderer||new Y,this.renderer=this.options.renderer,this.renderer.options=this.options,this.renderer.parser=this,this.textRenderer=new ee}static parse(e,t){return new te(t).parse(e)}static parseInline(e,t){return new te(t).parseInline(e)}parse(e,t=!0){let n="";for(let s=0;s{const r=e[s].flat(1/0);n=n.concat(this.walkTokens(r,t))})):e.tokens&&(n=n.concat(this.walkTokens(e.tokens,t)))}}return n}use(...e){const t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach((e=>{const n={...e};if(n.async=this.defaults.async||n.async||!1,e.extensions&&(e.extensions.forEach((e=>{if(!e.name)throw new Error("extension name required");if("renderer"in e){const n=t.renderers[e.name];t.renderers[e.name]=n?function(...t){let s=e.renderer.apply(this,t);return!1===s&&(s=n.apply(this,t)),s}:e.renderer}if("tokenizer"in e){if(!e.level||"block"!==e.level&&"inline"!==e.level)throw new Error("extension level must be 'block' or 'inline'");const n=t[e.level];n?n.unshift(e.tokenizer):t[e.level]=[e.tokenizer],e.start&&("block"===e.level?t.startBlock?t.startBlock.push(e.start):t.startBlock=[e.start]:"inline"===e.level&&(t.startInline?t.startInline.push(e.start):t.startInline=[e.start]))}"childTokens"in e&&e.childTokens&&(t.childTokens[e.name]=e.childTokens)})),n.extensions=t),e.renderer){const t=this.defaults.renderer||new Y(this.defaults);for(const n in e.renderer){if(!(n in t))throw new Error(`renderer '${n}' does not exist`);if(["options","parser"].includes(n))continue;const s=n,r=e.renderer[s],i=t[s];t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n||""}}n.renderer=t}if(e.tokenizer){const t=this.defaults.tokenizer||new V(this.defaults);for(const n in e.tokenizer){if(!(n in t))throw new Error(`tokenizer '${n}' does not exist`);if(["options","rules","lexer"].includes(n))continue;const s=n,r=e.tokenizer[s],i=t[s];t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.tokenizer=t}if(e.hooks){const t=this.defaults.hooks||new ne;for(const n in e.hooks){if(!(n in t))throw new Error(`hook '${n}' does not exist`);if(["options","block"].includes(n))continue;const s=n,r=e.hooks[s],i=t[s];ne.passThroughHooks.has(n)?t[s]=e=>{if(this.defaults.async)return Promise.resolve(r.call(t,e)).then((e=>i.call(t,e)));const n=r.call(t,e);return i.call(t,n)}:t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.hooks=t}if(e.walkTokens){const t=this.defaults.walkTokens,s=e.walkTokens;n.walkTokens=function(e){let n=[];return n.push(s.call(this,e)),t&&(n=n.concat(t.call(this,e))),n}}this.defaults={...this.defaults,...n}})),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return W.lex(e,t??this.defaults)}parser(e,t){return te.parse(e,t??this.defaults)}parseMarkdown(e){return(t,n)=>{const s={...n},r={...this.defaults,...s},i=this.onError(!!r.silent,!!r.async);if(!0===this.defaults.async&&!1===s.async)return i(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(null==t)return i(new Error("marked(): input parameter is undefined or null"));if("string"!=typeof t)return i(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(t)+", string expected"));r.hooks&&(r.hooks.options=r,r.hooks.block=e);const l=r.hooks?r.hooks.provideLexer():e?W.lex:W.lexInline,o=r.hooks?r.hooks.provideParser():e?te.parse:te.parseInline;if(r.async)return Promise.resolve(r.hooks?r.hooks.preprocess(t):t).then((e=>l(e,r))).then((e=>r.hooks?r.hooks.processAllTokens(e):e)).then((e=>r.walkTokens?Promise.all(this.walkTokens(e,r.walkTokens)).then((()=>e)):e)).then((e=>o(e,r))).then((e=>r.hooks?r.hooks.postprocess(e):e)).catch(i);try{r.hooks&&(t=r.hooks.preprocess(t));let e=l(t,r);r.hooks&&(e=r.hooks.processAllTokens(e)),r.walkTokens&&this.walkTokens(e,r.walkTokens);let n=o(e,r);return r.hooks&&(n=r.hooks.postprocess(n)),n}catch(e){return i(e)}}}onError(e,t){return n=>{if(n.message+="\nPlease report this to https://github.com/markedjs/marked.",e){const e="An error occurred:
"+X(n.message+"",!0)+" ";return t?Promise.resolve(e):e}if(t)return Promise.reject(n);throw n}}}const re=new se;function ie(e,t){return re.parse(e,t)}ie.options=ie.setOptions=function(e){return re.setOptions(e),ie.defaults=re.defaults,n(ie.defaults),ie},ie.getDefaults=t,ie.defaults=e.defaults,ie.use=function(...e){return re.use(...e),ie.defaults=re.defaults,n(ie.defaults),ie},ie.walkTokens=function(e,t){return re.walkTokens(e,t)},ie.parseInline=re.parseInline,ie.Parser=te,ie.parser=te.parse,ie.Renderer=Y,ie.TextRenderer=ee,ie.Lexer=W,ie.lexer=W.lex,ie.Tokenizer=V,ie.Hooks=ne,ie.parse=ie;const le=ie.options,oe=ie.setOptions,ae=ie.use,ce=ie.walkTokens,he=ie.parseInline,pe=ie,ue=te.parse,ge=W.lex;e.Hooks=ne,e.Lexer=W,e.Marked=se,e.Parser=te,e.Renderer=Y,e.TextRenderer=ee,e.Tokenizer=V,e.getDefaults=t,e.lexer=ge,e.marked=ie,e.options=le,e.parse=pe,e.parseInline=he,e.parser=ue,e.setOptions=oe,e.use=ae,e.walkTokens=ce}));
+
+/**
+ * DO NOT EDIT THIS FILE
+ * The code in this file is generated from files in ./src/
+ */
+(function(g,f){if(typeof exports=="object"&&typeof module<"u"){module.exports=f()}else if("function"==typeof define && define.amd){define("marked",f)}else {g["marked"]=f()}}(typeof globalThis < "u" ? globalThis : typeof self < "u" ? self : this,function(){var exports={};var __exports=exports;var module={exports};
+"use strict";var N=Object.defineProperty;var Oe=Object.getOwnPropertyDescriptor;var we=Object.getOwnPropertyNames;var ye=Object.prototype.hasOwnProperty;var Pe=(l,e)=>{for(var t in e)N(l,t,{get:e[t],enumerable:!0})},Se=(l,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of we(e))!ye.call(l,s)&&s!==t&&N(l,s,{get:()=>e[s],enumerable:!(n=Oe(e,s))||n.enumerable});return l};var $e=l=>Se(N({},"__esModule",{value:!0}),l);var Rt={};Pe(Rt,{Hooks:()=>P,Lexer:()=>x,Marked:()=>C,Parser:()=>b,Renderer:()=>y,TextRenderer:()=>S,Tokenizer:()=>w,defaults:()=>T,getDefaults:()=>_,lexer:()=>bt,marked:()=>g,options:()=>ht,parse:()=>mt,parseInline:()=>ft,parser:()=>xt,setOptions:()=>kt,use:()=>dt,walkTokens:()=>gt});module.exports=$e(Rt);function _(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}var T=_();function Q(l){T=l}var z={exec:()=>null};function E(l){let e=[];return t=>{let n=Math.max(0,Math.min(3,t-1)),s=e[n];return s||(s=l(n),e[n]=s),s}}function d(l,e=""){let t=typeof l=="string"?l:l.source,n={replace:(s,r)=>{let i=typeof r=="string"?r:r.source;return i=i.replace(m.caret,"$1"),t=t.replace(s,i),n},getRegex:()=>new RegExp(t,e)};return n}var Le=((l="")=>{try{return!!new RegExp("(?<=1)(?/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] +\S/,listReplaceTask:/^\[[ xX]\] +/,listTaskCheckbox:/\[[ xX]\]/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^,endAngleBracket:/>$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:l=>new RegExp(`^( {0,3}${l})((?:[ ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:E(l=>new RegExp(`^ {0,${l}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`)),hrRegex:E(l=>new RegExp(`^ {0,${l}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`)),fencesBeginRegex:E(l=>new RegExp(`^ {0,${l}}(?:\`\`\`|~~~)`)),headingBeginRegex:E(l=>new RegExp(`^ {0,${l}}#`)),htmlBeginRegex:E(l=>new RegExp(`^ {0,${l}}<(?:[a-z].*>|!--)`,"i")),blockquoteBeginRegex:E(l=>new RegExp(`^ {0,${l}}>`))},_e=/^(?:[ \t]*(?:\n|$))+/,ze=/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,Me=/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,D=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,Ee=/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,F=/ {0,3}(?:[*+-]|\d{1,9}[.)])/,ae=/^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,le=d(ae).replace(/bull/g,F).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/\|table/g,"").getRegex(),Ie=d(ae).replace(/bull/g,F).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/table/g,/ {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(),U=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,Ae=/^[^\n]+/,K=/(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/,Ce=d(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",K).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),Be=d(/^(bull)([ \t][^\n]*?)?(?:\n|$)/).replace(/bull/g,F).getRegex(),H="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",W=/|$))/,De=d("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))","i").replace("comment",W).replace("tag",H).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),ue=d(U).replace("hr",D).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)])[ \\t]+[^ \\t\\n]").replace("html","?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",H).getRegex(),qe=d(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",ue).getRegex(),X={blockquote:qe,code:ze,def:Ce,fences:Me,heading:Ee,hr:D,html:De,lheading:le,list:Be,newline:_e,paragraph:ue,table:z,text:Ae},ie=d("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",D).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code","(?: {4}| {0,3} )[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html","?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",H).getRegex(),ve={...X,lheading:Ie,table:ie,paragraph:d(U).replace("hr",D).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",ie).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)])[ \\t]+[^ \\t\\n]").replace("html","?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",H).getRegex()},He={...X,html:d(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?\\1> *(?:\\n{2,}|\\s*$)| \\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment",W).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:z,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:d(U).replace("hr",D).replace("heading",` *#{1,6} *[^
+]`).replace("lheading",le).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},Ze=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,Ge=/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,pe=/^( {2,}|\\)\n(?!\s*$)/,Ne=/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\`+)[^`]+\k (?!`))*?\]\((?:\\[\s\S]|[^\\\(\)]|\((?:\\[\s\S]|[^\\\(\)])*\))*\)/).replace("precode-",Le?"(?`+)[^`]+\k(?!`)/).replace("html",/<(?! )[^<>]*?>/).getRegex(),he=/^(?:\*+(?:((?!\*)punct)|([^\s*]))?)|^_+(?:((?!_)punct)|([^\s_]))?/,Ke=d(he,"u").replace(/punct/g,I).getRegex(),We=d(he,"u").replace(/punct/g,ce).getRegex(),ke="^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)",Xe=d(ke,"gu").replace(/notPunctSpace/g,J).replace(/punctSpace/g,Z).replace(/punct/g,I).getRegex(),Je=d(ke,"gu").replace(/notPunctSpace/g,Fe).replace(/punctSpace/g,je).replace(/punct/g,ce).getRegex(),Ve=d("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)","gu").replace(/notPunctSpace/g,J).replace(/punctSpace/g,Z).replace(/punct/g,I).getRegex(),Ye=d(/^~~?(?:((?!~)punct)|[^\s~])/,"u").replace(/punct/g,I).getRegex(),et="^[^~]+(?=[^~])|(?!~)punct(~~?)(?=[\\s]|$)|notPunctSpace(~~?)(?!~)(?=punctSpace|$)|(?!~)punctSpace(~~?)(?=notPunctSpace)|[\\s](~~?)(?!~)(?=punct)|(?!~)punct(~~?)(?!~)(?=punct)|notPunctSpace(~~?)(?=notPunctSpace)",tt=d(et,"gu").replace(/notPunctSpace/g,J).replace(/punctSpace/g,Z).replace(/punct/g,I).getRegex(),nt=d(/\\(punct)/,"gu").replace(/punct/g,I).getRegex(),rt=d(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),st=d(W).replace("(?:-->|$)","-->").getRegex(),it=d("^comment|^[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^").replace("comment",st).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),v=/(?:\[(?:\\[\s\S]|[^\[\]\\])*\]|\\[\s\S]|`+(?!`)[^`]*?`+(?!`)|``+(?=\])|[^\[\]\\`])*?/,ot=d(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]+(?:\n[ \t]*)?|\n[ \t]*)(title))?\s*\)/).replace("label",v).replace("href",/<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),de=d(/^!?\[(label)\]\[(ref)\]/).replace("label",v).replace("ref",K).getRegex(),ge=d(/^!?\[(ref)\](?:\[\])?/).replace("ref",K).getRegex(),at=d("reflink|nolink(?!\\()","g").replace("reflink",de).replace("nolink",ge).getRegex(),oe=/[hH][tT][tT][pP][sS]?|[fF][tT][pP]/,V={_backpedal:z,anyPunctuation:nt,autolink:rt,blockSkip:Ue,br:pe,code:Ge,del:z,delLDelim:z,delRDelim:z,emStrongLDelim:Ke,emStrongRDelimAst:Xe,emStrongRDelimUnd:Ve,escape:Ze,link:ot,nolink:ge,punctuation:Qe,reflink:de,reflinkSearch:at,tag:it,text:Ne,url:z},lt={...V,link:d(/^!?\[(label)\]\((.*?)\)/).replace("label",v).getRegex(),reflink:d(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",v).getRegex()},j={...V,emStrongRDelimAst:Je,emStrongLDelim:We,delLDelim:Ye,delRDelim:tt,url:d(/^((?:protocol):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/).replace("protocol",oe).replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])((?:\\[\s\S]|[^\\])*?(?:\\[\s\S]|[^\s~\\]))\1(?=[^~]|$)/,text:d(/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\":">",'"':""","'":"'"},fe=l=>pt[l];function O(l,e){if(e){if(m.escapeTest.test(l))return l.replace(m.escapeReplace,fe)}else if(m.escapeTestNoEncode.test(l))return l.replace(m.escapeReplaceNoEncode,fe);return l}function Y(l){try{l=encodeURI(l).replace(m.percentDecode,"%")}catch{return null}return l}function ee(l,e){let t=l.replace(m.findPipe,(r,i,o)=>{let u=!1,a=i;for(;--a>=0&&o[a]==="\\";)u=!u;return u?"|":" |"}),n=t.split(m.splitPipe),s=0;if(n[0].trim()||n.shift(),n.length>0&&!n.at(-1)?.trim()&&n.pop(),e)if(n.length>e)n.splice(e);else for(;n.length=0&&m.blankLine.test(e[t]);)t--;return e.length-t<=2?l:e.slice(0,t+1).join(`
+`)}function me(l,e){if(l.indexOf(e[1])===-1)return-1;let t=0;for(let n=0;n0?-2:-1}function xe(l,e=0){let t=e,n="";for(let s of l)if(s===" "){let r=4-t%4;n+=" ".repeat(r),t+=r}else n+=s,t++;return n}function be(l,e,t,n,s){let r=e.href,i=e.title||null,o=l[1].replace(s.other.outputLinkReplace,"$1");n.state.inLink=!0;let u={type:l[0].charAt(0)==="!"?"image":"link",raw:t,href:r,title:i,text:o,tokens:n.inlineTokens(o)};return n.state.inLink=!1,u}function ct(l,e,t){let n=l.match(t.other.indentCodeCompensation);if(n===null)return e;let s=n[1];return e.split(`
+`).map(r=>{let i=r.match(t.other.beginningSpace);if(i===null)return r;let[o]=i;return o.length>=s.length?r.slice(s.length):r}).join(`
+`)}var w=class{options;rules;lexer;constructor(e){this.options=e||T}space(e){let t=this.rules.block.newline.exec(e);if(t&&t[0].length>0)return{type:"space",raw:t[0]}}code(e){let t=this.rules.block.code.exec(e);if(t){let n=this.options.pedantic?t[0]:te(t[0]),s=n.replace(this.rules.other.codeRemoveIndent,"");return{type:"code",raw:n,codeBlockStyle:"indented",text:s}}}fences(e){let t=this.rules.block.fences.exec(e);if(t){let n=t[0],s=ct(n,t[3]||"",this.rules);return{type:"code",raw:n,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:s}}}heading(e){let t=this.rules.block.heading.exec(e);if(t){let n=t[2].trim();if(this.rules.other.endingHash.test(n)){let s=L(n,"#");(this.options.pedantic||!s||this.rules.other.endingSpaceChar.test(s))&&(n=s.trim())}return{type:"heading",raw:L(t[0],`
+`),depth:t[1].length,text:n,tokens:this.lexer.inline(n)}}}hr(e){let t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:L(t[0],`
+`)}}blockquote(e){let t=this.rules.block.blockquote.exec(e);if(t){let n=L(t[0],`
+`).split(`
+`),s="",r="",i=[];for(;n.length>0;){let o=!1,u=[],a;for(a=0;a1,r={type:"list",raw:"",ordered:s,start:s?+n.slice(0,-1):"",loose:!1,items:[]};n=s?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=s?n:"[*+-]");let i=this.rules.other.listItemRegex(n),o=!1;for(;e;){let a=!1,c="",p="";if(!(t=i.exec(e))||this.rules.block.hr.test(e))break;c=t[0],e=e.substring(c.length);let k=xe(t[2].split(`
+`,1)[0],t[1].length),h=e.split(`
+`,1)[0],R=!k.trim(),f=0;if(this.options.pedantic?(f=2,p=k.trimStart()):R?f=t[1].length+1:(f=k.search(this.rules.other.nonSpaceChar),f=f>4?1:f,p=k.slice(f),f+=t[1].length),R&&this.rules.other.blankLine.test(h)&&(c+=h+`
+`,e=e.substring(h.length+1),a=!0),!a){let $=this.rules.other.nextBulletRegex(f),ne=this.rules.other.hrRegex(f),re=this.rules.other.fencesBeginRegex(f),se=this.rules.other.headingBeginRegex(f),Re=this.rules.other.htmlBeginRegex(f),Te=this.rules.other.blockquoteBeginRegex(f);for(;e;){let G=e.split(`
+`,1)[0],B;if(h=G,this.options.pedantic?(h=h.replace(this.rules.other.listReplaceNesting," "),B=h):B=h.replace(this.rules.other.tabCharGlobal," "),re.test(h)||se.test(h)||Re.test(h)||Te.test(h)||$.test(h)||ne.test(h))break;if(B.search(this.rules.other.nonSpaceChar)>=f||!h.trim())p+=`
+`+B.slice(f);else{if(R||k.replace(this.rules.other.tabCharGlobal," ").search(this.rules.other.nonSpaceChar)>=4||re.test(k)||se.test(k)||ne.test(k))break;p+=`
+`+h}R=!h.trim(),c+=G+`
+`,e=e.substring(G.length+1),k=B.slice(f)}}r.loose||(o?r.loose=!0:this.rules.other.doubleBlankLine.test(c)&&(o=!0)),r.items.push({type:"list_item",raw:c,task:!!this.options.gfm&&this.rules.other.listIsTask.test(p),loose:!1,text:p,tokens:[]}),r.raw+=c}let u=r.items.at(-1);if(u)u.raw=u.raw.trimEnd(),u.text=u.text.trimEnd();else return;r.raw=r.raw.trimEnd();for(let a of r.items){this.lexer.state.top=!1,a.tokens=this.lexer.blockTokens(a.text,[]);let c=a.tokens[0];if(a.task&&(c?.type==="text"||c?.type==="paragraph")){a.text=a.text.replace(this.rules.other.listReplaceTask,""),c.raw=c.raw.replace(this.rules.other.listReplaceTask,""),c.text=c.text.replace(this.rules.other.listReplaceTask,"");for(let k=this.lexer.inlineQueue.length-1;k>=0;k--)if(this.rules.other.listIsTask.test(this.lexer.inlineQueue[k].src)){this.lexer.inlineQueue[k].src=this.lexer.inlineQueue[k].src.replace(this.rules.other.listReplaceTask,"");break}let p=this.rules.other.listTaskCheckbox.exec(a.raw);if(p){let k={type:"checkbox",raw:p[0]+" ",checked:p[0]!=="[ ]"};a.checked=k.checked,r.loose?a.tokens[0]&&["paragraph","text"].includes(a.tokens[0].type)&&"tokens"in a.tokens[0]&&a.tokens[0].tokens?(a.tokens[0].raw=k.raw+a.tokens[0].raw,a.tokens[0].text=k.raw+a.tokens[0].text,a.tokens[0].tokens.unshift(k)):a.tokens.unshift({type:"paragraph",raw:k.raw,text:k.raw,tokens:[k]}):a.tokens.unshift(k)}}else a.task&&(a.task=!1);if(!r.loose){let p=a.tokens.filter(h=>h.type==="space"),k=p.length>0&&p.some(h=>this.rules.other.anyLine.test(h.raw));r.loose=k}}if(r.loose)for(let a of r.items){a.loose=!0;for(let c of a.tokens)c.type==="text"&&(c.type="paragraph")}return r}}html(e){let t=this.rules.block.html.exec(e);if(t){let n=te(t[0]);return{type:"html",block:!0,raw:n,pre:t[1]==="pre"||t[1]==="script"||t[1]==="style",text:n}}}def(e){let t=this.rules.block.def.exec(e);if(t){let n=t[1].toLowerCase().replace(this.rules.other.multipleSpaceGlobal," "),s=t[2]?t[2].replace(this.rules.other.hrefBrackets,"$1").replace(this.rules.inline.anyPunctuation,"$1"):"",r=t[3]?t[3].substring(1,t[3].length-1).replace(this.rules.inline.anyPunctuation,"$1"):t[3];return{type:"def",tag:n,raw:L(t[0],`
+`),href:s,title:r}}}table(e){let t=this.rules.block.table.exec(e);if(!t||!this.rules.other.tableDelimiter.test(t[2]))return;let n=ee(t[1]),s=t[2].replace(this.rules.other.tableAlignChars,"").split("|"),r=t[3]?.trim()?t[3].replace(this.rules.other.tableRowBlankLine,"").split(`
+`):[],i={type:"table",raw:L(t[0],`
+`),header:[],align:[],rows:[]};if(n.length===s.length){for(let o of s)this.rules.other.tableAlignRight.test(o)?i.align.push("right"):this.rules.other.tableAlignCenter.test(o)?i.align.push("center"):this.rules.other.tableAlignLeft.test(o)?i.align.push("left"):i.align.push(null);for(let o=0;o({text:u,tokens:this.lexer.inline(u),header:!1,align:i.align[a]})));return i}}lheading(e){let t=this.rules.block.lheading.exec(e);if(t){let n=t[1].trim();return{type:"heading",raw:L(t[0],`
+`),depth:t[2].charAt(0)==="="?1:2,text:n,tokens:this.lexer.inline(n)}}}paragraph(e){let t=this.rules.block.paragraph.exec(e);if(t){let n=t[1].charAt(t[1].length-1)===`
+`?t[1].slice(0,-1):t[1];return{type:"paragraph",raw:t[0],text:n,tokens:this.lexer.inline(n)}}}text(e){let t=this.rules.block.text.exec(e);if(t)return{type:"text",raw:t[0],text:t[0],tokens:this.lexer.inline(t[0])}}escape(e){let t=this.rules.inline.escape.exec(e);if(t)return{type:"escape",raw:t[0],text:t[1]}}tag(e){let t=this.rules.inline.tag.exec(e);if(t)return!this.lexer.state.inLink&&this.rules.other.startATag.test(t[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&this.rules.other.endATag.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&this.rules.other.startPreScriptTag.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&this.rules.other.endPreScriptTag.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:t[0]}}link(e){let t=this.rules.inline.link.exec(e);if(t){let n=t[2].trim();if(!this.options.pedantic&&this.rules.other.startAngleBracket.test(n)){if(!this.rules.other.endAngleBracket.test(n))return;let i=L(n.slice(0,-1),"\\");if((n.length-i.length)%2===0)return}else{let i=me(t[2],"()");if(i===-2)return;if(i>-1){let u=(t[0].indexOf("!")===0?5:4)+t[1].length+i;t[2]=t[2].substring(0,i),t[0]=t[0].substring(0,u).trim(),t[3]=""}}let s=t[2],r="";if(this.options.pedantic){let i=this.rules.other.pedanticHrefTitle.exec(s);i&&(s=i[1],r=i[3])}else r=t[3]?t[3].slice(1,-1):"";return s=s.trim(),this.rules.other.startAngleBracket.test(s)&&(this.options.pedantic&&!this.rules.other.endAngleBracket.test(n)?s=s.slice(1):s=s.slice(1,-1)),be(t,{href:s&&s.replace(this.rules.inline.anyPunctuation,"$1"),title:r&&r.replace(this.rules.inline.anyPunctuation,"$1")},t[0],this.lexer,this.rules)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){let s=(n[2]||n[1]).replace(this.rules.other.multipleSpaceGlobal," "),r=t[s.toLowerCase()];if(!r){let i=n[0].charAt(0);return{type:"text",raw:i,text:i}}return be(n,r,n[0],this.lexer,this.rules)}}emStrong(e,t,n=""){let s=this.rules.inline.emStrongLDelim.exec(e);if(!s||!s[1]&&!s[2]&&!s[3]&&!s[4]||s[4]&&n.match(this.rules.other.unicodeAlphaNumeric))return;if(!(s[1]||s[3]||"")||!n||this.rules.inline.punctuation.exec(n)){let i=[...s[0]].length-1,o,u,a=i,c=0,p=s[0][0]==="*"?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(p.lastIndex=0,t=t.slice(-1*e.length+i);(s=p.exec(t))!==null;){if(o=s[1]||s[2]||s[3]||s[4]||s[5]||s[6],!o)continue;if(u=[...o].length,s[3]||s[4]){a+=u;continue}else if((s[5]||s[6])&&i%3&&!((i+u)%3)){c+=u;continue}if(a-=u,a>0)continue;u=Math.min(u,u+a+c);let k=[...s[0]][0].length,h=e.slice(0,i+s.index+k+u);if(Math.min(i,u)%2){let f=h.slice(1,-1);return{type:"em",raw:h,text:f,tokens:this.lexer.inlineTokens(f)}}let R=h.slice(2,-2);return{type:"strong",raw:h,text:R,tokens:this.lexer.inlineTokens(R)}}}}codespan(e){let t=this.rules.inline.code.exec(e);if(t){let n=t[2].replace(this.rules.other.newLineCharGlobal," "),s=this.rules.other.nonSpaceChar.test(n),r=this.rules.other.startingSpaceChar.test(n)&&this.rules.other.endingSpaceChar.test(n);return s&&r&&(n=n.substring(1,n.length-1)),{type:"codespan",raw:t[0],text:n}}}br(e){let t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e,t,n=""){let s=this.rules.inline.delLDelim.exec(e);if(!s)return;if(!(s[1]||"")||!n||this.rules.inline.punctuation.exec(n)){let i=[...s[0]].length-1,o,u,a=i,c=this.rules.inline.delRDelim;for(c.lastIndex=0,t=t.slice(-1*e.length+i);(s=c.exec(t))!==null;){if(o=s[1]||s[2]||s[3]||s[4]||s[5]||s[6],!o||(u=[...o].length,u!==i))continue;if(s[3]||s[4]){a+=u;continue}if(a-=u,a>0)continue;u=Math.min(u,u+a);let p=[...s[0]][0].length,k=e.slice(0,i+s.index+p+u),h=k.slice(i,-i);return{type:"del",raw:k,text:h,tokens:this.lexer.inlineTokens(h)}}}}autolink(e){let t=this.rules.inline.autolink.exec(e);if(t){let n,s;return t[2]==="@"?(n=t[1],s="mailto:"+n):(n=t[1],s=n),{type:"link",raw:t[0],text:n,href:s,tokens:[{type:"text",raw:n,text:n}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let n,s;if(t[2]==="@")n=t[0],s="mailto:"+n;else{let r;do r=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])?.[0]??"";while(r!==t[0]);n=t[0],t[1]==="www."?s="http://"+t[0]:s=t[0]}return{type:"link",raw:t[0],text:n,href:s,tokens:[{type:"text",raw:n,text:n}]}}}inlineText(e){let t=this.rules.inline.text.exec(e);if(t){let n=this.lexer.state.inRawBlock;return{type:"text",raw:t[0],text:t[0],escaped:n}}}};var x=class l{tokens;options;state;inlineQueue;tokenizer;constructor(e){this.tokens=[],this.tokens.links=Object.create(null),this.options=e||T,this.options.tokenizer=this.options.tokenizer||new w,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};let t={other:m,block:q.normal,inline:A.normal};this.options.pedantic?(t.block=q.pedantic,t.inline=A.pedantic):this.options.gfm&&(t.block=q.gfm,this.options.breaks?t.inline=A.breaks:t.inline=A.gfm),this.tokenizer.rules=t}static get rules(){return{block:q,inline:A}}static lex(e,t){return new l(t).lex(e)}static lexInline(e,t){return new l(t).inlineTokens(e)}lex(e){e=e.replace(m.carriageReturn,`
+`),this.blockTokens(e,this.tokens);for(let t=0;t(r=o.call({lexer:this},e,t))?(e=e.substring(r.raw.length),t.push(r),!0):!1))continue;if(r=this.tokenizer.space(e)){e=e.substring(r.raw.length);let o=t.at(-1);r.raw.length===1&&o!==void 0?o.raw+=`
+`:t.push(r);continue}if(r=this.tokenizer.code(e)){e=e.substring(r.raw.length);let o=t.at(-1);o?.type==="paragraph"||o?.type==="text"?(o.raw+=(o.raw.endsWith(`
+`)?"":`
+`)+r.raw,o.text+=`
+`+r.text,this.inlineQueue.at(-1).src=o.text):t.push(r);continue}if(r=this.tokenizer.fences(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.heading(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.hr(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.blockquote(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.list(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.html(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.def(e)){e=e.substring(r.raw.length);let o=t.at(-1);o?.type==="paragraph"||o?.type==="text"?(o.raw+=(o.raw.endsWith(`
+`)?"":`
+`)+r.raw,o.text+=`
+`+r.raw,this.inlineQueue.at(-1).src=o.text):this.tokens.links[r.tag]||(this.tokens.links[r.tag]={href:r.href,title:r.title},t.push(r));continue}if(r=this.tokenizer.table(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.lheading(e)){e=e.substring(r.raw.length),t.push(r);continue}let i=e;if(this.options.extensions?.startBlock){let o=1/0,u=e.slice(1),a;this.options.extensions.startBlock.forEach(c=>{a=c.call({lexer:this},u),typeof a=="number"&&a>=0&&(o=Math.min(o,a))}),o<1/0&&o>=0&&(i=e.substring(0,o+1))}if(this.state.top&&(r=this.tokenizer.paragraph(i))){let o=t.at(-1);n&&o?.type==="paragraph"?(o.raw+=(o.raw.endsWith(`
+`)?"":`
+`)+r.raw,o.text+=`
+`+r.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=o.text):t.push(r),n=i.length!==e.length,e=e.substring(r.raw.length);continue}if(r=this.tokenizer.text(e)){e=e.substring(r.raw.length);let o=t.at(-1);o?.type==="text"?(o.raw+=(o.raw.endsWith(`
+`)?"":`
+`)+r.raw,o.text+=`
+`+r.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=o.text):t.push(r);continue}if(e){this.infiniteLoopError(e.charCodeAt(0));break}}return this.state.top=!0,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){this.tokenizer.lexer=this;let n=e,s=null;if(this.tokens.links){let a=Object.keys(this.tokens.links);if(a.length>0)for(;(s=this.tokenizer.rules.inline.reflinkSearch.exec(n))!==null;)a.includes(s[0].slice(s[0].lastIndexOf("[")+1,-1))&&(n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;(s=this.tokenizer.rules.inline.anyPunctuation.exec(n))!==null;)n=n.slice(0,s.index)+"++"+n.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);let r;for(;(s=this.tokenizer.rules.inline.blockSkip.exec(n))!==null;)r=s[2]?s[2].length:0,n=n.slice(0,s.index+r)+"["+"a".repeat(s[0].length-r-2)+"]"+n.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);n=this.options.hooks?.emStrongMask?.call({lexer:this},n)??n;let i=!1,o="",u=1/0;for(;e;){if(e.length(a=p.call({lexer:this},e,t))?(e=e.substring(a.raw.length),t.push(a),!0):!1))continue;if(a=this.tokenizer.escape(e)){e=e.substring(a.raw.length),t.push(a);continue}if(a=this.tokenizer.tag(e)){e=e.substring(a.raw.length),t.push(a);continue}if(a=this.tokenizer.link(e)){e=e.substring(a.raw.length),t.push(a);continue}if(a=this.tokenizer.reflink(e,this.tokens.links)){e=e.substring(a.raw.length);let p=t.at(-1);a.type==="text"&&p?.type==="text"?(p.raw+=a.raw,p.text+=a.text):t.push(a);continue}if(a=this.tokenizer.emStrong(e,n,o)){e=e.substring(a.raw.length),t.push(a);continue}if(a=this.tokenizer.codespan(e)){e=e.substring(a.raw.length),t.push(a);continue}if(a=this.tokenizer.br(e)){e=e.substring(a.raw.length),t.push(a);continue}if(a=this.tokenizer.del(e,n,o)){e=e.substring(a.raw.length),t.push(a);continue}if(a=this.tokenizer.autolink(e)){e=e.substring(a.raw.length),t.push(a);continue}if(!this.state.inLink&&(a=this.tokenizer.url(e))){e=e.substring(a.raw.length),t.push(a);continue}let c=e;if(this.options.extensions?.startInline){let p=1/0,k=e.slice(1),h;this.options.extensions.startInline.forEach(R=>{h=R.call({lexer:this},k),typeof h=="number"&&h>=0&&(p=Math.min(p,h))}),p<1/0&&p>=0&&(c=e.substring(0,p+1))}if(a=this.tokenizer.inlineText(c)){e=e.substring(a.raw.length),a.raw.slice(-1)!=="_"&&(o=a.raw.slice(-1)),i=!0;let p=t.at(-1);p?.type==="text"?(p.raw+=a.raw,p.text+=a.text):t.push(a);continue}if(e){this.infiniteLoopError(e.charCodeAt(0));break}}return t}infiniteLoopError(e){let t="Infinite loop on byte: "+e;if(this.options.silent)console.error(t);else throw new Error(t)}};var y=class{options;parser;constructor(e){this.options=e||T}space(e){return""}code({text:e,lang:t,escaped:n}){let s=(t||"").match(m.notSpaceStart)?.[0],r=e.replace(m.endingNewline,"")+`
+`;return s?''+(n?r:O(r,!0))+`
+`:""+(n?r:O(r,!0))+`
+`}blockquote({tokens:e}){return`
+${this.parser.parse(e)}
+`}html({text:e}){return e}def(e){return""}heading({tokens:e,depth:t}){return`${this.parser.parseInline(e)}
+`}hr(e){return`
+`}list(e){let t=e.ordered,n=e.start,s="";for(let o=0;o
+`+s+""+r+`>
+`}listitem(e){return`${this.parser.parse(e.tokens)}
+`}checkbox({checked:e}){return" '}paragraph({tokens:e}){return`${this.parser.parseInline(e)}
+`}table(e){let t="",n="";for(let r=0;r${s}`),`
+`}tablerow({text:e}){return`
+${e}
+`}tablecell(e){let t=this.parser.parseInline(e.tokens),n=e.header?"th":"td";return(e.align?`<${n} align="${e.align}">`:`<${n}>`)+t+`${n}>
+`}strong({tokens:e}){return`${this.parser.parseInline(e)} `}em({tokens:e}){return`${this.parser.parseInline(e)} `}codespan({text:e}){return`${O(e,!0)}`}br(e){return" "}del({tokens:e}){return`${this.parser.parseInline(e)}`}link({href:e,title:t,tokens:n}){let s=this.parser.parseInline(n),r=Y(e);if(r===null)return s;e=r;let i='"+s+" ",i}image({href:e,title:t,text:n,tokens:s}){s&&(n=this.parser.parseInline(s,this.parser.textRenderer));let r=Y(e);if(r===null)return O(n);e=r;let i=` ",i}text(e){return"tokens"in e&&e.tokens?this.parser.parseInline(e.tokens):"escaped"in e&&e.escaped?e.text:O(e.text)}};var S=class{strong({text:e}){return e}em({text:e}){return e}codespan({text:e}){return e}del({text:e}){return e}html({text:e}){return e}text({text:e}){return e}link({text:e}){return""+e}image({text:e}){return""+e}br(){return""}checkbox({raw:e}){return e}};var b=class l{options;renderer;textRenderer;constructor(e){this.options=e||T,this.options.renderer=this.options.renderer||new y,this.renderer=this.options.renderer,this.renderer.options=this.options,this.renderer.parser=this,this.textRenderer=new S}static parse(e,t){return new l(t).parse(e)}static parseInline(e,t){return new l(t).parseInline(e)}parse(e){this.renderer.parser=this;let t="";for(let n=0;n{let o=r[i].flat(1/0);n=n.concat(this.walkTokens(o,t))}):r.tokens&&(n=n.concat(this.walkTokens(r.tokens,t)))}}return n}use(...e){let t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach(n=>{let s={...n};if(s.async=this.defaults.async||s.async||!1,n.extensions&&(n.extensions.forEach(r=>{if(!r.name)throw new Error("extension name required");if("renderer"in r){let i=t.renderers[r.name];i?t.renderers[r.name]=function(...o){let u=r.renderer.apply(this,o);return u===!1&&(u=i.apply(this,o)),u}:t.renderers[r.name]=r.renderer}if("tokenizer"in r){if(!r.level||r.level!=="block"&&r.level!=="inline")throw new Error("extension level must be 'block' or 'inline'");let i=t[r.level];i?i.unshift(r.tokenizer):t[r.level]=[r.tokenizer],r.start&&(r.level==="block"?t.startBlock?t.startBlock.push(r.start):t.startBlock=[r.start]:r.level==="inline"&&(t.startInline?t.startInline.push(r.start):t.startInline=[r.start]))}"childTokens"in r&&r.childTokens&&(t.childTokens[r.name]=r.childTokens)}),s.extensions=t),n.renderer){let r=this.defaults.renderer||new y(this.defaults);for(let i in n.renderer){if(!(i in r))throw new Error(`renderer '${i}' does not exist`);if(["options","parser"].includes(i))continue;let o=i,u=n.renderer[o],a=r[o];r[o]=(...c)=>{let p=u.apply(r,c);return p===!1&&(p=a.apply(r,c)),p||""}}s.renderer=r}if(n.tokenizer){let r=this.defaults.tokenizer||new w(this.defaults);for(let i in n.tokenizer){if(!(i in r))throw new Error(`tokenizer '${i}' does not exist`);if(["options","rules","lexer"].includes(i))continue;let o=i,u=n.tokenizer[o],a=r[o];r[o]=(...c)=>{let p=u.apply(r,c);return p===!1&&(p=a.apply(r,c)),p}}s.tokenizer=r}if(n.hooks){let r=this.defaults.hooks||new P;for(let i in n.hooks){if(!(i in r))throw new Error(`hook '${i}' does not exist`);if(["options","block"].includes(i))continue;let o=i,u=n.hooks[o],a=r[o];P.passThroughHooks.has(i)?r[o]=c=>{if(this.defaults.async&&P.passThroughHooksRespectAsync.has(i))return(async()=>{let k=await u.call(r,c);return a.call(r,k)})();let p=u.call(r,c);return a.call(r,p)}:r[o]=(...c)=>{if(this.defaults.async)return(async()=>{let k=await u.apply(r,c);return k===!1&&(k=await a.apply(r,c)),k})();let p=u.apply(r,c);return p===!1&&(p=a.apply(r,c)),p}}s.hooks=r}if(n.walkTokens){let r=this.defaults.walkTokens,i=n.walkTokens;s.walkTokens=function(o){let u=[];return u.push(i.call(this,o)),r&&(u=u.concat(r.call(this,o))),u}}this.defaults={...this.defaults,...s}}),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return x.lex(e,t??this.defaults)}parser(e,t){return b.parse(e,t??this.defaults)}parseMarkdown(e){return(n,s)=>{let r={...s},i={...this.defaults,...r},o=this.onError(!!i.silent,!!i.async);if(this.defaults.async===!0&&r.async===!1)return o(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(typeof n>"u"||n===null)return o(new Error("marked(): input parameter is undefined or null"));if(typeof n!="string")return o(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(n)+", string expected"));if(i.hooks&&(i.hooks.options=i,i.hooks.block=e),i.async)return(async()=>{let u=i.hooks?await i.hooks.preprocess(n):n,c=await(i.hooks?await i.hooks.provideLexer(e):e?x.lex:x.lexInline)(u,i),p=i.hooks?await i.hooks.processAllTokens(c):c;i.walkTokens&&await Promise.all(this.walkTokens(p,i.walkTokens));let h=await(i.hooks?await i.hooks.provideParser(e):e?b.parse:b.parseInline)(p,i);return i.hooks?await i.hooks.postprocess(h):h})().catch(o);try{i.hooks&&(n=i.hooks.preprocess(n));let a=(i.hooks?i.hooks.provideLexer(e):e?x.lex:x.lexInline)(n,i);i.hooks&&(a=i.hooks.processAllTokens(a)),i.walkTokens&&this.walkTokens(a,i.walkTokens);let p=(i.hooks?i.hooks.provideParser(e):e?b.parse:b.parseInline)(a,i);return i.hooks&&(p=i.hooks.postprocess(p)),p}catch(u){return o(u)}}}onError(e,t){return n=>{if(n.message+=`
+Please report this to https://github.com/markedjs/marked.`,e){let s="An error occurred:
"+O(n.message+"",!0)+" ";return t?Promise.resolve(s):s}if(t)return Promise.reject(n);throw n}}};var M=new C;function g(l,e){return M.parse(l,e)}g.options=g.setOptions=function(l){return M.setOptions(l),g.defaults=M.defaults,Q(g.defaults),g};g.getDefaults=_;g.defaults=T;g.use=function(...l){return M.use(...l),g.defaults=M.defaults,Q(g.defaults),g};g.walkTokens=function(l,e){return M.walkTokens(l,e)};g.parseInline=M.parseInline;g.Parser=b;g.parser=b.parse;g.Renderer=y;g.TextRenderer=S;g.Lexer=x;g.lexer=x.lex;g.Tokenizer=w;g.Hooks=P;g.parse=g;var ht=g.options,kt=g.setOptions,dt=g.use,gt=g.walkTokens,ft=g.parseInline,mt=g,xt=b.parse,bt=x.lex;
+
+if(__exports != exports)module.exports = exports;return module.exports}));
diff --git a/packages/coding-agent/src/core/extensions/loader.ts b/packages/coding-agent/src/core/extensions/loader.ts
index 37f66164..a93f7c85 100644
--- a/packages/coding-agent/src/core/extensions/loader.ts
+++ b/packages/coding-agent/src/core/extensions/loader.ts
@@ -127,6 +127,30 @@ function getAliases(): Record {
type HandlerFn = (...args: unknown[]) => Promise;
+let extensionCacheCwd: string | undefined;
+let extensionCacheGeneration = 0;
+const extensionCache = new Map();
+
+interface ExtensionCacheToken {
+ cwd: string;
+ generation: number;
+}
+
+export function clearExtensionCache(): void {
+ extensionCache.clear();
+ extensionCacheCwd = undefined;
+ extensionCacheGeneration++;
+}
+
+function useExtensionCacheCwd(cwd: string): ExtensionCacheToken {
+ const resolvedCwd = resolvePath(cwd);
+ if (extensionCacheCwd !== undefined && extensionCacheCwd !== resolvedCwd) {
+ clearExtensionCache();
+ }
+ extensionCacheCwd = resolvedCwd;
+ return { cwd: resolvedCwd, generation: extensionCacheGeneration };
+}
+
/**
* Create a runtime with throwing stubs for action methods.
* Runner.bindCore() replaces these with real implementations.
@@ -338,7 +362,22 @@ function createExtensionAPI(
return api;
}
-async function loadExtensionModule(extensionPath: string) {
+function isCurrentCacheToken(cacheToken: ExtensionCacheToken | undefined): cacheToken is ExtensionCacheToken {
+ return (
+ cacheToken !== undefined &&
+ extensionCacheCwd === cacheToken.cwd &&
+ extensionCacheGeneration === cacheToken.generation
+ );
+}
+
+async function loadExtensionModule(extensionPath: string, cacheToken?: ExtensionCacheToken) {
+ if (isCurrentCacheToken(cacheToken)) {
+ const cachedFactory = extensionCache.get(extensionPath);
+ if (cachedFactory) {
+ return cachedFactory;
+ }
+ }
+
const jiti = createJiti(import.meta.url, {
moduleCache: false,
// In Bun binary: use virtualModules for bundled packages (no filesystem resolution)
@@ -349,7 +388,13 @@ async function loadExtensionModule(extensionPath: string) {
const module = await jiti.import(extensionPath, { default: true });
const factory = module as ExtensionFactory;
- return typeof factory !== "function" ? undefined : factory;
+ if (typeof factory !== "function") {
+ return undefined;
+ }
+ if (isCurrentCacheToken(cacheToken)) {
+ extensionCache.set(extensionPath, factory);
+ }
+ return factory;
}
/**
@@ -380,11 +425,12 @@ async function loadExtension(
cwd: string,
eventBus: EventBus,
runtime: ExtensionRuntime,
+ cacheToken?: ExtensionCacheToken,
): Promise<{ extension: Extension | null; error: string | null }> {
const resolvedPath = resolvePath(extensionPath, cwd, { normalizeUnicodeSpaces: true });
try {
- const factory = await loadExtensionModule(resolvedPath);
+ const factory = await loadExtensionModule(resolvedPath, cacheToken);
if (!factory) {
return { extension: null, error: `Extension does not export a valid factory function: ${extensionPath}` };
}
@@ -420,20 +466,28 @@ export async function loadExtensionFromFactory(
/**
* Load extensions from paths.
*/
-export async function loadExtensions(
+async function loadExtensionsInternal(
paths: string[],
cwd: string,
eventBus?: EventBus,
runtime?: ExtensionRuntime,
+ useCache = false,
): Promise {
const extensions: Extension[] = [];
const errors: Array<{ path: string; error: string }> = [];
- const resolvedCwd = resolvePath(cwd);
+ const cacheToken = useCache ? useExtensionCacheCwd(cwd) : undefined;
+ const resolvedCwd = cacheToken?.cwd ?? resolvePath(cwd);
const resolvedEventBus = eventBus ?? createEventBus();
const resolvedRuntime = runtime ?? createExtensionRuntime();
for (const extPath of paths) {
- const { extension, error } = await loadExtension(extPath, resolvedCwd, resolvedEventBus, resolvedRuntime);
+ const { extension, error } = await loadExtension(
+ extPath,
+ resolvedCwd,
+ resolvedEventBus,
+ resolvedRuntime,
+ cacheToken,
+ );
if (error) {
errors.push({ path: extPath, error });
@@ -452,6 +506,24 @@ export async function loadExtensions(
};
}
+export async function loadExtensions(
+ paths: string[],
+ cwd: string,
+ eventBus?: EventBus,
+ runtime?: ExtensionRuntime,
+): Promise {
+ return loadExtensionsInternal(paths, cwd, eventBus, runtime);
+}
+
+export async function loadExtensionsCached(
+ paths: string[],
+ cwd: string,
+ eventBus?: EventBus,
+ runtime?: ExtensionRuntime,
+): Promise {
+ return loadExtensionsInternal(paths, cwd, eventBus, runtime, true);
+}
+
interface PiManifest {
extensions?: string[];
themes?: string[];
diff --git a/packages/coding-agent/src/core/extensions/types.ts b/packages/coding-agent/src/core/extensions/types.ts
index a869a55d..7234d4e4 100644
--- a/packages/coding-agent/src/core/extensions/types.ts
+++ b/packages/coding-agent/src/core/extensions/types.ts
@@ -571,6 +571,10 @@ export interface SessionBeforeCompactEvent {
preparation: CompactionPreparation;
branchEntries: SessionEntry[];
customInstructions?: string;
+ /** What triggered the compaction: manual /compact, the context threshold, or context overflow recovery */
+ reason: "manual" | "threshold" | "overflow";
+ /** True when the aborted turn is retried after this compaction (overflow recovery) */
+ willRetry: boolean;
signal: AbortSignal;
}
@@ -579,6 +583,10 @@ export interface SessionCompactEvent {
type: "session_compact";
compactionEntry: CompactionEntry;
fromExtension: boolean;
+ /** What triggered the compaction: manual /compact, the context threshold, or context overflow recovery */
+ reason: "manual" | "threshold" | "overflow";
+ /** True when the aborted turn is retried after this compaction (overflow recovery) */
+ willRetry: boolean;
}
/** Fired before an extension runtime is torn down due to quit, reload, or session replacement. */
diff --git a/packages/coding-agent/src/core/http-dispatcher.ts b/packages/coding-agent/src/core/http-dispatcher.ts
index 12ce9fb0..0910f4d6 100644
--- a/packages/coding-agent/src/core/http-dispatcher.ts
+++ b/packages/coding-agent/src/core/http-dispatcher.ts
@@ -10,6 +10,9 @@ export const HTTP_IDLE_TIMEOUT_CHOICES = [
{ label: "disabled", timeoutMs: 0 },
] as const;
+const originalGlobalFetch = globalThis.fetch;
+let installedGlobalFetch: typeof globalThis.fetch | undefined;
+
export function parseHttpIdleTimeoutMs(value: unknown): number | undefined {
if (typeof value === "string") {
const trimmed = value.trim();
@@ -36,6 +39,13 @@ export function formatHttpIdleTimeoutMs(timeoutMs: number): string {
return `${timeoutMs / 1000} sec`;
}
+export function applyHttpProxySettings(httpProxy: string | undefined): void {
+ const proxy = httpProxy?.trim();
+ if (!proxy) return;
+ process.env.HTTP_PROXY ??= proxy;
+ process.env.HTTPS_PROXY ??= proxy;
+}
+
export function configureHttpDispatcher(timeoutMs: number = DEFAULT_HTTP_IDLE_TIMEOUT_MS): void {
const normalizedTimeoutMs = parseHttpIdleTimeoutMs(timeoutMs);
if (normalizedTimeoutMs === undefined) {
@@ -51,5 +61,13 @@ export function configureHttpDispatcher(timeoutMs: number = DEFAULT_HTTP_IDLE_TI
// Keep fetch and the dispatcher on the same undici implementation. Node 26.0's
// bundled fetch can otherwise consume compressed responses through npm undici's
// dispatcher without decompressing them, causing response.json() failures.
- undici.install?.();
+ // If a caller replaced fetch after module load, preserve that deliberate override.
+ const shouldInstallGlobals =
+ installedGlobalFetch === undefined
+ ? globalThis.fetch === originalGlobalFetch
+ : globalThis.fetch === installedGlobalFetch;
+ if (shouldInstallGlobals) {
+ undici.install?.();
+ installedGlobalFetch = globalThis.fetch;
+ }
}
diff --git a/packages/coding-agent/src/core/model-registry.ts b/packages/coding-agent/src/core/model-registry.ts
index 39246d18..8f86bf6c 100644
--- a/packages/coding-agent/src/core/model-registry.ts
+++ b/packages/coding-agent/src/core/model-registry.ts
@@ -25,7 +25,6 @@ import { type Static, Type } from "typebox";
import { Compile } from "typebox/compile";
import type { TLocalizedValidationError } from "typebox/error";
import { getAgentDir } from "../config.ts";
-import { warnDeprecation } from "../utils/deprecation.ts";
import { stripJsonComments } from "../utils/json.ts";
import { normalizePath } from "../utils/paths.ts";
import type { AuthStatus, AuthStorage } from "./auth-storage.ts";
@@ -35,7 +34,6 @@ import {
getConfigValueEnvVarNames,
isCommandConfigValue,
isConfigValueConfigured,
- isLegacyEnvVarNameConfigValue,
resolveConfigValueOrThrow,
resolveConfigValueUncached,
resolveHeadersOrThrow,
@@ -98,6 +96,13 @@ const ThinkingLevelMapSchema = Type.Object({
xhigh: Type.Optional(ThinkingLevelMapValueSchema),
});
+const ChatTemplateKwargScalarSchema = Type.Union([Type.String(), Type.Number(), Type.Boolean(), Type.Null()]);
+const ChatTemplateKwargVariableSchema = Type.Object({
+ $var: Type.Union([Type.Literal("thinking.enabled"), Type.Literal("thinking.effort")]),
+ omitWhenOff: Type.Optional(Type.Boolean()),
+});
+const ChatTemplateKwargSchema = Type.Union([ChatTemplateKwargScalarSchema, ChatTemplateKwargVariableSchema]);
+
const OpenAICompletionsCompatSchema = Type.Object({
supportsStore: Type.Optional(Type.Boolean()),
supportsDeveloperRole: Type.Optional(Type.Boolean()),
@@ -116,9 +121,13 @@ const OpenAICompletionsCompatSchema = Type.Object({
Type.Literal("deepseek"),
Type.Literal("zai"),
Type.Literal("qwen"),
+ Type.Literal("chat-template"),
Type.Literal("qwen-chat-template"),
+ Type.Literal("string-thinking"),
+ Type.Literal("ant-ling"),
]),
),
+ chatTemplateKwargs: Type.Optional(Type.Record(Type.String(), ChatTemplateKwargSchema)),
cacheControlFormat: Type.Optional(Type.Literal("anthropic")),
openRouterRouting: Type.Optional(OpenRouterRoutingSchema),
vercelGatewayRouting: Type.Optional(VercelGatewayRoutingSchema),
@@ -237,82 +246,12 @@ interface ProviderRequestConfig {
authHeader?: boolean;
}
-function migrateLegacyRegisterProviderConfigValue(providerName: string, field: string, value: string): string {
- if (!isLegacyEnvVarNameConfigValue(value)) return value;
- warnDeprecation(
- `registerProvider("${providerName}") ${field} value "${value}" is treated as a legacy environment variable reference. This will no longer be detected as an environment variable reference in a future release. Pass "$${value}" instead.`,
- );
- return `$${value}`;
-}
-
-function migrateLegacyRegisterProviderHeaders(
- providerName: string,
- field: string,
- headers: Record | undefined,
-): Record | undefined {
- if (!headers) return undefined;
- let migratedHeaders: Record | undefined;
- for (const [key, value] of Object.entries(headers)) {
- const migratedValue = migrateLegacyRegisterProviderConfigValue(providerName, `${field} header "${key}"`, value);
- if (migratedValue === value) continue;
- migratedHeaders ??= { ...headers };
- migratedHeaders[key] = migratedValue;
- }
- return migratedHeaders ?? headers;
-}
-
-function migrateLegacyRegisterProviderConfigValues(
- providerName: string,
- config: ProviderConfigInput,
-): ProviderConfigInput {
- let migratedConfig: ProviderConfigInput | undefined;
-
- const setMigratedConfigValue = (
- key: TKey,
- value: ProviderConfigInput[TKey],
- ) => {
- migratedConfig ??= { ...config };
- migratedConfig[key] = value;
- };
-
- if (config.apiKey) {
- const apiKey = migrateLegacyRegisterProviderConfigValue(providerName, "apiKey", config.apiKey);
- if (apiKey !== config.apiKey) {
- setMigratedConfigValue("apiKey", apiKey);
- }
- }
-
- const headers = migrateLegacyRegisterProviderHeaders(providerName, "headers", config.headers);
- if (headers !== config.headers) {
- setMigratedConfigValue("headers", headers);
- }
-
- if (config.models) {
- let models: ProviderConfigInput["models"] | undefined;
- for (let index = 0; index < config.models.length; index++) {
- const model = config.models[index];
- const modelHeaders = migrateLegacyRegisterProviderHeaders(
- providerName,
- `model "${model.id}" headers`,
- model.headers,
- );
- if (modelHeaders === model.headers) continue;
- models ??= [...config.models];
- models[index] = { ...model, headers: modelHeaders };
- }
- if (models) {
- setMigratedConfigValue("models", models);
- }
- }
-
- return migratedConfig ?? config;
-}
-
export type ResolvedRequestAuth =
| {
ok: true;
apiKey?: string;
headers?: Record;
+ env?: Record;
}
| {
ok: false;
@@ -361,6 +300,13 @@ function mergeCompat(
};
}
+ if (baseCompletions?.chatTemplateKwargs || overrideCompletions.chatTemplateKwargs) {
+ mergedCompletions.chatTemplateKwargs = {
+ ...baseCompletions?.chatTemplateKwargs,
+ ...overrideCompletions.chatTemplateKwargs,
+ };
+ }
+
return merged as Model["compat"];
}
@@ -757,17 +703,27 @@ export class ModelRegistry {
async getApiKeyAndHeaders(model: Model): Promise {
try {
const providerConfig = this.providerRequestConfigs.get(model.provider);
- const apiKeyFromAuthStorage = await this.authStorage.getApiKey(model.provider);
+ const providerEnv = this.authStorage.getProviderEnv(model.provider);
+ const apiKeyFromAuthStorage = await this.authStorage.getApiKey(model.provider, { includeFallback: false });
const apiKey =
apiKeyFromAuthStorage ??
(providerConfig?.apiKey
- ? resolveConfigValueOrThrow(providerConfig.apiKey, `API key for provider "${model.provider}"`)
+ ? resolveConfigValueOrThrow(
+ providerConfig.apiKey,
+ `API key for provider "${model.provider}"`,
+ providerEnv,
+ )
: undefined);
- const providerHeaders = resolveHeadersOrThrow(providerConfig?.headers, `provider "${model.provider}"`);
+ const providerHeaders = resolveHeadersOrThrow(
+ providerConfig?.headers,
+ `provider "${model.provider}"`,
+ providerEnv,
+ );
const modelHeaders = resolveHeadersOrThrow(
this.modelRequestHeaders.get(this.getModelRequestKey(model.provider, model.id)),
`model "${model.provider}/${model.id}"`,
+ providerEnv,
);
let headers =
@@ -786,6 +742,7 @@ export class ModelRegistry {
ok: true,
apiKey,
headers: headers && Object.keys(headers).length > 0 ? headers : undefined,
+ env: providerEnv && Object.keys(providerEnv).length > 0 ? providerEnv : undefined,
};
} catch (error) {
return {
@@ -850,7 +807,9 @@ export class ModelRegistry {
}
const providerApiKey = this.providerRequestConfigs.get(provider)?.apiKey;
- return providerApiKey ? resolveConfigValueUncached(providerApiKey) : undefined;
+ return providerApiKey
+ ? resolveConfigValueUncached(providerApiKey, this.authStorage.getProviderEnv(provider))
+ : undefined;
}
/**
@@ -869,10 +828,9 @@ export class ModelRegistry {
* If provider has oauth: registers OAuth provider for /login support.
*/
registerProvider(providerName: string, config: ProviderConfigInput): void {
- const migratedConfig = migrateLegacyRegisterProviderConfigValues(providerName, config);
- this.validateProviderConfig(providerName, migratedConfig);
- this.applyProviderConfig(providerName, migratedConfig);
- this.upsertRegisteredProvider(providerName, migratedConfig);
+ this.validateProviderConfig(providerName, config);
+ this.applyProviderConfig(providerName, config);
+ this.upsertRegisteredProvider(providerName, config);
}
/**
diff --git a/packages/coding-agent/src/core/model-resolver.ts b/packages/coding-agent/src/core/model-resolver.ts
index c9afb7b2..0017df00 100644
--- a/packages/coding-agent/src/core/model-resolver.ts
+++ b/packages/coding-agent/src/core/model-resolver.ts
@@ -340,7 +340,7 @@ export interface ResolveCliModelResult {
export function resolveCliModel(options: {
cliProvider?: string;
cliModel?: string;
- cliThinking?: string;
+ cliThinking?: ThinkingLevel;
modelRegistry: ModelRegistry;
}): ResolveCliModelResult {
const { cliProvider, cliModel, cliThinking, modelRegistry } = options;
@@ -422,6 +422,27 @@ export function resolveCliModel(options: {
});
if (model) {
+ // If provider inference matched an unauthenticated provider/model pair, prefer
+ // one exact raw model-id match that is authenticated. This keeps
+ // "provider/model" syntax preferred when usable, but handles models whose
+ // literal id starts with a known provider name (for example
+ // commandcode model id "xiaomi/mimo-v2.5-pro").
+ if (inferredProvider) {
+ const rawExactMatches = availableModels.filter(
+ (m) => m.id.toLowerCase() === cliModel.toLowerCase() && !modelsAreEqual(m, model),
+ );
+ if (rawExactMatches.length > 0 && !modelRegistry.hasConfiguredAuth(model)) {
+ const authenticatedRawMatches = rawExactMatches.filter((m) => modelRegistry.hasConfiguredAuth(m));
+ if (authenticatedRawMatches.length === 1) {
+ return {
+ model: authenticatedRawMatches[0],
+ thinkingLevel: undefined,
+ warning: undefined,
+ error: undefined,
+ };
+ }
+ }
+ }
return { model, thinkingLevel, warning, error: undefined };
}
@@ -470,10 +491,13 @@ export function resolveCliModel(options: {
const fallbackModel = buildFallbackModel(provider, fallbackPattern, availableModels);
if (fallbackModel) {
+ const requestedThinking = cliThinking ?? fallbackThinking;
+ const model =
+ requestedThinking && requestedThinking !== "off" ? { ...fallbackModel, reasoning: true } : fallbackModel;
const fallbackWarning = warning
? `${warning} Model "${fallbackPattern}" not found for provider "${provider}". Using custom model id.`
: `Model "${fallbackPattern}" not found for provider "${provider}". Using custom model id.`;
- return { model: fallbackModel, thinkingLevel: fallbackThinking, warning: fallbackWarning, error: undefined };
+ return { model, thinkingLevel: fallbackThinking, warning: fallbackWarning, error: undefined };
}
}
diff --git a/packages/coding-agent/src/core/package-manager.ts b/packages/coding-agent/src/core/package-manager.ts
index 5120c9ad..ebdf974b 100644
--- a/packages/coding-agent/src/core/package-manager.ts
+++ b/packages/coding-agent/src/core/package-manager.ts
@@ -27,6 +27,7 @@ import type { Readable } from "node:stream";
import { globSync } from "glob";
import ignore from "ignore";
import { minimatch } from "minimatch";
+import { maxSatisfying, rcompare, satisfies, valid, validRange } from "semver";
import { CONFIG_DIR_NAME } from "../config.ts";
import { spawnProcess, spawnProcessSync } from "../utils/child-process.ts";
import { type GitSource, parseGitUrl } from "../utils/git.ts";
@@ -44,6 +45,14 @@ function isOfflineModeEnabled(): boolean {
return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes";
}
+function isExactNpmVersion(version: string | undefined): boolean {
+ return valid(version ?? "") !== null;
+}
+
+function getNpmVersionRange(version: string | undefined): string | undefined {
+ return version ? (validRange(version) ?? undefined) : undefined;
+}
+
export interface PathMetadata {
source: string;
scope: SourceScope;
@@ -119,6 +128,8 @@ type NpmSource = {
type: "npm";
spec: string;
name: string;
+ version?: string;
+ range?: string;
pinned: boolean;
};
@@ -1113,8 +1124,8 @@ export class DefaultPackageManager implements PackageManager {
}
try {
- const latestVersion = await this.getLatestNpmVersion(source.name);
- return latestVersion !== installedVersion;
+ const targetVersion = await this.getLatestNpmVersion(source.version ? source.spec : source.name, source.range);
+ return targetVersion !== installedVersion;
} catch {
// Preserve existing update behavior when version lookup fails.
return true;
@@ -1128,7 +1139,7 @@ export class DefaultPackageManager implements PackageManager {
const sourceLabel = sources.length === 1 ? sources[0].source : `${scope} npm packages`;
const message = sources.length === 1 ? `Updating ${sources[0].source}...` : `Updating ${scope} npm packages...`;
- const specs = sources.map((entry) => `${entry.parsed.name}@latest`);
+ const specs = sources.map((entry) => (entry.parsed.version ? entry.parsed.spec : `${entry.parsed.name}@latest`));
await this.withProgress("update", sourceLabel, message, async () => {
await this.installNpmBatch(specs, scope);
@@ -1241,8 +1252,7 @@ export class DefaultPackageManager implements PackageManager {
if (parsed.type === "npm") {
let installedPath = this.getNpmInstallPath(parsed, scope);
const needsInstall =
- !existsSync(installedPath) ||
- (parsed.pinned && !(await this.installedNpmMatchesPinnedVersion(parsed, installedPath)));
+ !existsSync(installedPath) || !(await this.installedNpmMatchesConfiguredVersion(parsed, installedPath));
if (needsInstall) {
const installed = await installMissing();
if (!installed) continue;
@@ -1394,7 +1404,9 @@ export class DefaultPackageManager implements PackageManager {
type: "npm",
spec,
name,
- pinned: Boolean(version),
+ version,
+ range: getNpmVersionRange(version),
+ pinned: isExactNpmVersion(version),
};
}
@@ -1411,18 +1423,12 @@ export class DefaultPackageManager implements PackageManager {
return { type: "local", path: source };
}
- private async installedNpmMatchesPinnedVersion(source: NpmSource, installedPath: string): Promise {
+ private async installedNpmMatchesConfiguredVersion(source: NpmSource, installedPath: string): Promise {
const installedVersion = this.getInstalledNpmVersion(installedPath);
if (!installedVersion) {
return false;
}
-
- const { version: pinnedVersion } = this.parseNpmSpec(source.spec);
- if (!pinnedVersion) {
- return true;
- }
-
- return installedVersion === pinnedVersion;
+ return source.range ? satisfies(installedVersion, source.range) : true;
}
private async npmHasAvailableUpdate(source: NpmSource, installedPath: string): Promise {
@@ -1436,8 +1442,8 @@ export class DefaultPackageManager implements PackageManager {
}
try {
- const latestVersion = await this.getLatestNpmVersion(source.name);
- return latestVersion !== installedVersion;
+ const targetVersion = await this.getLatestNpmVersion(source.version ? source.spec : source.name, source.range);
+ return targetVersion !== installedVersion;
} catch {
return false;
}
@@ -1455,16 +1461,25 @@ export class DefaultPackageManager implements PackageManager {
}
}
- private async getLatestNpmVersion(packageName: string): Promise {
+ private async getLatestNpmVersion(packageSpec: string, range?: string): Promise {
const npmCommand = this.getNpmCommand();
const stdout = await this.runCommandCapture(
npmCommand.command,
- [...npmCommand.args, "view", packageName, "version", "--json"],
+ [...npmCommand.args, "view", packageSpec, "version", "--json"],
{ cwd: this.cwd, timeoutMs: NETWORK_TIMEOUT_MS },
);
const raw = stdout.trim();
if (!raw) throw new Error("Empty response from npm view");
- return JSON.parse(raw);
+ const parsed = JSON.parse(raw) as unknown;
+ if (typeof parsed === "string") {
+ return parsed;
+ }
+ if (Array.isArray(parsed)) {
+ const versions = parsed.filter((value): value is string => typeof value === "string" && value.length > 0);
+ const latest = range ? maxSatisfying(versions, range) : [...versions].sort(rcompare)[0];
+ if (latest) return latest;
+ }
+ throw new Error("Unexpected response from npm view");
}
private async gitHasAvailableUpdate(installedPath: string): Promise {
diff --git a/packages/coding-agent/src/core/project-trust.ts b/packages/coding-agent/src/core/project-trust.ts
index c8b57250..2521ad5d 100644
--- a/packages/coding-agent/src/core/project-trust.ts
+++ b/packages/coding-agent/src/core/project-trust.ts
@@ -1,9 +1,10 @@
+import { CONFIG_DIR_NAME } from "../config.ts";
import { emitProjectTrustEvent } from "./extensions/runner.ts";
import type { LoadExtensionsResult, ProjectTrustContext } from "./extensions/types.ts";
import type { DefaultProjectTrust } from "./settings-manager.ts";
import {
getProjectTrustOptions,
- hasProjectTrustInputs,
+ hasTrustRequiringProjectResources,
type ProjectTrustOption,
type ProjectTrustStore,
} from "./trust-manager.ts";
@@ -21,7 +22,7 @@ export interface ResolveProjectTrustedOptions {
}
function formatProjectTrustPrompt(cwd: string): string {
- return `Trust project folder?\n${cwd}\n\nThis allows pi to load .pi settings and resources, install missing project packages, and execute project extensions.`;
+ return `Trust project folder?\n${cwd}\n\nThis allows pi to load ${CONFIG_DIR_NAME} settings and resources, install missing project packages, and execute project extensions.`;
}
async function selectProjectTrustOption(
@@ -46,7 +47,7 @@ export async function resolveProjectTrusted(options: ResolveProjectTrustedOption
if (options.trustOverride !== undefined) {
return options.trustOverride;
}
- if (!hasProjectTrustInputs(options.cwd)) {
+ if (!hasTrustRequiringProjectResources(options.cwd)) {
return true;
}
diff --git a/packages/coding-agent/src/core/provider-attribution.ts b/packages/coding-agent/src/core/provider-attribution.ts
index 91265c92..97ad1cfa 100644
--- a/packages/coding-agent/src/core/provider-attribution.ts
+++ b/packages/coding-agent/src/core/provider-attribution.ts
@@ -7,6 +7,7 @@ const NVIDIA_NIM_HOST = "integrate.api.nvidia.com";
const CLOUDFLARE_API_HOST = "api.cloudflare.com";
const CLOUDFLARE_AI_GATEWAY_HOST = "gateway.ai.cloudflare.com";
const OPENCODE_HOST = "opencode.ai";
+const VERCEL_GATEWAY_HOST = "ai-gateway.vercel.sh";
function matchesHost(baseUrl: string, expectedHost: string): boolean {
try {
@@ -33,6 +34,10 @@ function isCloudflareModel(model: Model): boolean {
);
}
+function isVercelGatewayModel(model: Model): boolean {
+ return model.provider === "vercel-ai-gateway" || matchesHost(model.baseUrl, VERCEL_GATEWAY_HOST);
+}
+
function getDefaultAttributionHeaders(
model: Model,
settingsManager: SettingsManager,
@@ -61,6 +66,13 @@ function getDefaultAttributionHeaders(
};
}
+ if (isVercelGatewayModel(model)) {
+ return {
+ "http-referer": "https://pi.dev",
+ "x-title": "pi",
+ };
+ }
+
return undefined;
}
diff --git a/packages/coding-agent/src/core/resolve-config-value.ts b/packages/coding-agent/src/core/resolve-config-value.ts
index 119e379d..6d75b001 100644
--- a/packages/coding-agent/src/core/resolve-config-value.ts
+++ b/packages/coding-agent/src/core/resolve-config-value.ts
@@ -10,7 +10,6 @@ import { getShellConfig } from "../utils/shell.ts";
const commandResultCache = new Map();
const ENV_VAR_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
const ENV_VAR_NAME_PREFIX_RE = /^[A-Za-z_][A-Za-z0-9_]*/;
-const LEGACY_ENV_VAR_NAME_RE = /^[A-Z_][A-Z0-9_]*$/;
type TemplatePart = { type: "literal"; value: string } | { type: "env"; name: string };
@@ -86,8 +85,8 @@ function parseConfigValueReference(config: string): ConfigValueReference {
return { type: "template", parts: parseConfigValueTemplate(config) };
}
-function resolveEnvConfigValue(name: string): string | undefined {
- return process.env[name] || undefined;
+function resolveEnvConfigValue(name: string, env?: Record): string | undefined {
+ return env?.[name] || process.env[name] || undefined;
}
function getTemplateEnvVarNames(parts: TemplatePart[]): string[] {
@@ -99,14 +98,14 @@ function getTemplateEnvVarNames(parts: TemplatePart[]): string[] {
return names;
}
-function resolveTemplate(parts: TemplatePart[]): string | undefined {
+function resolveTemplate(parts: TemplatePart[], env?: Record): string | undefined {
let resolved = "";
for (const part of parts) {
if (part.type === "literal") {
resolved += part.value;
continue;
}
- const envValue = resolveEnvConfigValue(part.name);
+ const envValue = resolveEnvConfigValue(part.name, env);
if (envValue === undefined) return undefined;
resolved += envValue;
}
@@ -124,20 +123,16 @@ export function getConfigValueEnvVarNames(config: string): string[] {
return reference.type === "template" ? getTemplateEnvVarNames(reference.parts) : [];
}
-export function getMissingConfigValueEnvVarNames(config: string): string[] {
- return getConfigValueEnvVarNames(config).filter((name) => resolveEnvConfigValue(name) === undefined);
+export function getMissingConfigValueEnvVarNames(config: string, env?: Record): string[] {
+ return getConfigValueEnvVarNames(config).filter((name) => resolveEnvConfigValue(name, env) === undefined);
}
export function isCommandConfigValue(config: string): boolean {
return parseConfigValueReference(config).type === "command";
}
-export function isConfigValueConfigured(config: string): boolean {
- return getMissingConfigValueEnvVarNames(config).length === 0;
-}
-
-export function isLegacyEnvVarNameConfigValue(config: string): boolean {
- return LEGACY_ENV_VAR_NAME_RE.test(config);
+export function isConfigValueConfigured(config: string, env?: Record): boolean {
+ return getMissingConfigValueEnvVarNames(config, env).length === 0;
}
/**
@@ -147,21 +142,23 @@ export function isLegacyEnvVarNameConfigValue(config: string): boolean {
* - In non-command values, "$$" escapes a literal "$" and "$!" escapes a literal "!"
* - Otherwise treats the value as a literal
*/
-export function resolveConfigValue(config: string): string | undefined {
+export function resolveConfigValue(config: string, env?: Record): string | undefined {
const reference = parseConfigValueReference(config);
if (reference.type === "command") {
return executeCommand(reference.config);
}
- return resolveTemplate(reference.parts);
+ return resolveTemplate(reference.parts, env);
}
function executeWithConfiguredShell(command: string): { executed: boolean; value: string | undefined } {
try {
- const { shell, args } = getShellConfig();
- const result = spawnSync(shell, [...args, command], {
+ const { shell, args, commandTransport } = getShellConfig();
+ const commandFromStdin = commandTransport === "stdin";
+ const result = spawnSync(shell, commandFromStdin ? args : [...args, command], {
encoding: "utf-8",
+ input: commandFromStdin ? command : undefined,
timeout: 10000,
- stdio: ["ignore", "pipe", "ignore"],
+ stdio: [commandFromStdin ? "pipe" : "ignore", "pipe", "ignore"],
shell: false,
windowsHide: true,
});
@@ -221,16 +218,16 @@ function executeCommand(commandConfig: string): string | undefined {
/**
* Resolve all header values using the same resolution logic as API keys.
*/
-export function resolveConfigValueUncached(config: string): string | undefined {
+export function resolveConfigValueUncached(config: string, env?: Record): string | undefined {
const reference = parseConfigValueReference(config);
if (reference.type === "command") {
return executeCommandUncached(reference.config);
}
- return resolveTemplate(reference.parts);
+ return resolveTemplate(reference.parts, env);
}
-export function resolveConfigValueOrThrow(config: string, description: string): string {
- const resolvedValue = resolveConfigValueUncached(config);
+export function resolveConfigValueOrThrow(config: string, description: string, env?: Record): string {
+ const resolvedValue = resolveConfigValueUncached(config, env);
if (resolvedValue !== undefined) {
return resolvedValue;
}
@@ -241,7 +238,7 @@ export function resolveConfigValueOrThrow(config: string, description: string):
}
if (reference.type === "template") {
- const missingEnvVars = getMissingConfigValueEnvVarNames(config);
+ const missingEnvVars = getMissingConfigValueEnvVarNames(config, env);
if (missingEnvVars.length === 1) {
throw new Error(`Failed to resolve ${description} from environment variable: ${missingEnvVars[0]}`);
}
@@ -256,11 +253,14 @@ export function resolveConfigValueOrThrow(config: string, description: string):
/**
* Resolve all header values using the same resolution logic as API keys.
*/
-export function resolveHeaders(headers: Record | undefined): Record | undefined {
+export function resolveHeaders(
+ headers: Record | undefined,
+ env?: Record,
+): Record | undefined {
if (!headers) return undefined;
const resolved: Record = {};
for (const [key, value] of Object.entries(headers)) {
- const resolvedValue = resolveConfigValue(value);
+ const resolvedValue = resolveConfigValue(value, env);
if (resolvedValue) {
resolved[key] = resolvedValue;
}
@@ -271,11 +271,12 @@ export function resolveHeaders(headers: Record | undefined): Rec
export function resolveHeadersOrThrow(
headers: Record | undefined,
description: string,
+ env?: Record,
): Record | undefined {
if (!headers) return undefined;
const resolved: Record = {};
for (const [key, value] of Object.entries(headers)) {
- resolved[key] = resolveConfigValueOrThrow(value, `${description} header "${key}"`);
+ resolved[key] = resolveConfigValueOrThrow(value, `${description} header "${key}"`, env);
}
return Object.keys(resolved).length > 0 ? resolved : undefined;
}
diff --git a/packages/coding-agent/src/core/resource-loader.ts b/packages/coding-agent/src/core/resource-loader.ts
index b35787af..18486ead 100644
--- a/packages/coding-agent/src/core/resource-loader.ts
+++ b/packages/coding-agent/src/core/resource-loader.ts
@@ -9,7 +9,12 @@ export type { ResourceCollision, ResourceDiagnostic } from "./diagnostics.ts";
import { canonicalizePath, isLocalPath, resolvePath } from "../utils/paths.ts";
import { createEventBus, type EventBus } from "./event-bus.ts";
-import { createExtensionRuntime, loadExtensionFromFactory, loadExtensions } from "./extensions/loader.ts";
+import {
+ clearExtensionCache,
+ createExtensionRuntime,
+ loadExtensionFromFactory,
+ loadExtensionsCached,
+} from "./extensions/loader.ts";
import type { Extension, ExtensionFactory, ExtensionRuntime, LoadExtensionsResult } from "./extensions/types.ts";
import { DefaultPackageManager, type PathMetadata, type ResolvedResource } from "./package-manager.ts";
import type { PromptTemplate } from "./prompt-templates.ts";
@@ -206,6 +211,7 @@ export class DefaultResourceLoader implements ResourceLoader {
private extensionThemeSourceInfos: Map;
private lastPromptPaths: string[];
private lastThemePaths: string[];
+ private loaded: boolean;
constructor(options: DefaultResourceLoaderOptions) {
this.cwd = resolvePath(options.cwd);
@@ -252,6 +258,7 @@ export class DefaultResourceLoader implements ResourceLoader {
this.extensionThemeSourceInfos = new Map();
this.lastPromptPaths = [];
this.lastThemePaths = [];
+ this.loaded = false;
}
getExtensions(): LoadExtensionsResult {
@@ -331,6 +338,10 @@ export class DefaultResourceLoader implements ResourceLoader {
}
async reload(options?: ResourceLoaderReloadOptions): Promise {
+ if (this.loaded) {
+ clearExtensionCache();
+ }
+
let preTrustExtensions: LoadExtensionsResult | undefined;
if (options?.resolveProjectTrust) {
preTrustExtensions = await this.loadProjectTrustExtensions();
@@ -475,6 +486,7 @@ export class DefaultResourceLoader implements ResourceLoader {
this.appendSystemPrompt = this.appendSystemPromptOverride
? this.appendSystemPromptOverride(baseAppend)
: baseAppend;
+ this.loaded = true;
}
private async loadCurrentExtensionSet(options: { includeInlineFactories: boolean }): Promise {
@@ -487,7 +499,7 @@ export class DefaultResourceLoader implements ResourceLoader {
const extensionPaths = this.noExtensions
? cliEnabledExtensions
: this.mergePaths(cliEnabledExtensions, enabledExtensions);
- const extensionsResult = await loadExtensions(extensionPaths, this.cwd, this.eventBus);
+ const extensionsResult = await loadExtensionsCached(extensionPaths, this.cwd, this.eventBus);
if (!options.includeInlineFactories) {
return extensionsResult;
}
@@ -507,7 +519,7 @@ export class DefaultResourceLoader implements ResourceLoader {
preTrustExtensions: LoadExtensionsResult | undefined,
): Promise {
if (!preTrustExtensions) {
- const extensionsResult = await loadExtensions(extensionPaths, this.cwd, this.eventBus);
+ const extensionsResult = await loadExtensionsCached(extensionPaths, this.cwd, this.eventBus);
const inlineExtensions = await this.loadExtensionFactories(extensionsResult.runtime);
extensionsResult.extensions.push(...inlineExtensions.extensions);
extensionsResult.errors.push(...inlineExtensions.errors);
@@ -527,7 +539,7 @@ export class DefaultResourceLoader implements ResourceLoader {
const resolvedPath = this.resolveExtensionLoadPath(path);
return !preloadedByPath.has(resolvedPath) && !failedPreloadPaths.has(resolvedPath);
});
- const remainingExtensions = await loadExtensions(
+ const remainingExtensions = await loadExtensionsCached(
remainingPaths,
this.cwd,
this.eventBus,
diff --git a/packages/coding-agent/src/core/sdk.ts b/packages/coding-agent/src/core/sdk.ts
index 49d13535..3bec2c32 100644
--- a/packages/coding-agent/src/core/sdk.ts
+++ b/packages/coding-agent/src/core/sdk.ts
@@ -303,6 +303,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
if (!auth.ok) {
throw new Error(auth.error);
}
+ const env = auth.env || options?.env ? { ...(auth.env ?? {}), ...(options?.env ?? {}) } : undefined;
const providerRetrySettings = settingsManager.getProviderRetrySettings();
const httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs();
// SDKs treat timeout=0 as 0ms (immediate timeout), not "no timeout".
@@ -314,6 +315,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
return streamSimple(model, context, {
...options,
apiKey: auth.apiKey,
+ env,
timeoutMs,
websocketConnectTimeoutMs,
maxRetries: options?.maxRetries ?? providerRetrySettings.maxRetries,
diff --git a/packages/coding-agent/src/core/session-manager.ts b/packages/coding-agent/src/core/session-manager.ts
index c2bae164..b07968b3 100644
--- a/packages/coding-agent/src/core/session-manager.ts
+++ b/packages/coding-agent/src/core/session-manager.ts
@@ -357,9 +357,10 @@ export function buildSessionContext(
const path: SessionEntry[] = [];
let current: SessionEntry | undefined = leaf;
while (current) {
- path.unshift(current);
+ path.push(current);
current = current.parentId ? byId.get(current.parentId) : undefined;
}
+ path.reverse();
// Extract settings and find compaction
let thinkingLevel = "off";
@@ -1152,9 +1153,10 @@ export class SessionManager {
const startId = fromId ?? this.leafId;
let current = startId ? this.byId.get(startId) : undefined;
while (current) {
- path.unshift(current);
+ path.push(current);
current = current.parentId ? this.byId.get(current.parentId) : undefined;
}
+ path.reverse();
return path;
}
@@ -1290,8 +1292,16 @@ export class SessionManager {
throw new Error(`Entry ${leafId} not found`);
}
- // Filter out LabelEntry from path - we'll recreate them from the resolved map
- const pathWithoutLabels = path.filter((e) => e.type !== "label");
+ // Filter out LabelEntry from path - we'll recreate them from the resolved map.
+ // Because labels are real tree entries, later entries can be children of labels;
+ // removing labels requires re-chaining the retained path to avoid orphaned subtrees.
+ const pathWithoutLabels: SessionEntry[] = [];
+ let pathParentId: string | null = null;
+ for (const entry of path) {
+ if (entry.type === "label") continue;
+ pathWithoutLabels.push({ ...entry, parentId: pathParentId });
+ pathParentId = entry.id;
+ }
const newSessionId = createSessionId();
const timestamp = new Date().toISOString();
diff --git a/packages/coding-agent/src/core/settings-manager.ts b/packages/coding-agent/src/core/settings-manager.ts
index 8acd5998..a90916a4 100644
--- a/packages/coding-agent/src/core/settings-manager.ts
+++ b/packages/coding-agent/src/core/settings-manager.ts
@@ -117,6 +117,7 @@ export interface Settings {
markdown?: MarkdownSettings;
warnings?: WarningSettings;
sessionDir?: string; // Custom session storage directory (same format as --session-dir CLI flag)
+ httpProxy?: string; // Proxy URL applied as HTTP_PROXY and HTTPS_PROXY for Pi-managed HTTP clients
httpIdleTimeoutMs?: number; // HTTP header/body idle timeout in milliseconds; 0 disables it
websocketConnectTimeoutMs?: number; // WebSocket connect/open handshake timeout in milliseconds; 0 disables it
}
@@ -713,8 +714,15 @@ export class SettingsManager {
this.save();
}
+ getThemeSetting(): string | undefined {
+ const value = this.settings.theme;
+ if (typeof value === "string") return value;
+ return undefined;
+ }
+
getTheme(): string | undefined {
- return this.settings.theme;
+ const theme = this.getThemeSetting();
+ return theme?.includes("/") ? undefined : theme;
}
setTheme(theme: string): void {
diff --git a/packages/coding-agent/src/core/tools/bash.ts b/packages/coding-agent/src/core/tools/bash.ts
index d2bfacb9..da6934e7 100644
--- a/packages/coding-agent/src/core/tools/bash.ts
+++ b/packages/coding-agent/src/core/tools/bash.ts
@@ -66,7 +66,7 @@ export interface BashOperations {
export function createLocalBashOperations(options?: { shellPath?: string }): BashOperations {
return {
exec: async (command, cwd, { onData, signal, timeout, env }) => {
- const { shell, args } = getShellConfig(options?.shellPath);
+ const shellConfig = getShellConfig(options?.shellPath);
try {
await fsAccess(cwd, constants.F_OK);
} catch {
@@ -76,13 +76,18 @@ export function createLocalBashOperations(options?: { shellPath?: string }): Bas
throw new Error("aborted");
}
- const child = spawn(shell, [...args, command], {
+ const commandFromStdin = shellConfig.commandTransport === "stdin";
+ const child = spawn(shellConfig.shell, commandFromStdin ? shellConfig.args : [...shellConfig.args, command], {
cwd,
detached: process.platform !== "win32",
env: env ?? getShellEnv(),
- stdio: ["ignore", "pipe", "pipe"],
+ stdio: [commandFromStdin ? "pipe" : "ignore", "pipe", "pipe"],
windowsHide: true,
});
+ if (commandFromStdin) {
+ child.stdin?.on("error", () => {});
+ child.stdin?.end(command);
+ }
if (child.pid) trackDetachedChildPid(child.pid);
let timedOut = false;
let timeoutHandle: NodeJS.Timeout | undefined;
@@ -289,6 +294,7 @@ export function createBashToolDefinition(
const resolvedCommand = commandPrefix ? `${commandPrefix}\n${command}` : command;
const spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook);
const output = new OutputAccumulator({ tempFilePrefix: "pi-bash" });
+ let acceptingOutput = true;
let updateTimer: NodeJS.Timeout | undefined;
let updateDirty = false;
let lastUpdateAt = 0;
@@ -334,11 +340,13 @@ export function createBashToolDefinition(
}
const handleData = (data: Buffer) => {
+ if (!acceptingOutput) return;
output.append(data);
scheduleOutputUpdate();
};
const finishOutput = async () => {
+ acceptingOutput = false;
output.finish();
clearUpdateTimer();
emitOutputUpdate();
diff --git a/packages/coding-agent/src/core/tools/edit-diff.ts b/packages/coding-agent/src/core/tools/edit-diff.ts
index f280bf19..5a4d966b 100644
--- a/packages/coding-agent/src/core/tools/edit-diff.ts
+++ b/packages/coding-agent/src/core/tools/edit-diff.ts
@@ -1,6 +1,5 @@
/**
- * Shared diff computation utilities for the edit tool.
- * Used by both edit.ts (for execution) and tool-execution.ts (for preview rendering).
+ * Shared diff computation utilities for the edit and similar tools.
*/
import * as Diff from "diff";
@@ -54,6 +53,124 @@ export function normalizeForFuzzyMatch(text: string): string {
);
}
+function splitLinesWithEndings(content: string): string[] {
+ return content.match(/[^\n]*\n|[^\n]+/g) ?? [];
+}
+
+interface LineSpan {
+ start: number;
+ end: number;
+}
+
+interface MatchedEdit {
+ editIndex: number;
+ matchIndex: number;
+ matchLength: number;
+ newText: string;
+}
+
+type TextReplacement = Pick;
+
+function getLineSpans(content: string): LineSpan[] {
+ let offset = 0;
+ return splitLinesWithEndings(content).map((line) => {
+ const span = { start: offset, end: offset + line.length };
+ offset = span.end;
+ return span;
+ });
+}
+
+function getReplacementLineRange(lines: LineSpan[], replacement: TextReplacement) {
+ const replacementStart = replacement.matchIndex;
+ const replacementEnd = replacement.matchIndex + replacement.matchLength;
+
+ let startLine = -1;
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+ if (replacementStart >= line.start && replacementStart < line.end) {
+ startLine = i;
+ break;
+ }
+ }
+ if (startLine === -1) {
+ throw new Error("Replacement range is outside the base content.");
+ }
+
+ let endLine = startLine;
+ while (endLine < lines.length && lines[endLine].end < replacementEnd) {
+ endLine++;
+ }
+ if (endLine >= lines.length) {
+ throw new Error("Replacement range is outside the base content.");
+ }
+
+ return { startLine, endLine: endLine + 1 };
+}
+
+function applyReplacements(content: string, replacements: TextReplacement[], offset = 0): string {
+ let result = content;
+ for (let i = replacements.length - 1; i >= 0; i--) {
+ const replacement = replacements[i];
+ const matchIndex = replacement.matchIndex - offset;
+ result =
+ result.substring(0, matchIndex) + replacement.newText + result.substring(matchIndex + replacement.matchLength);
+ }
+ return result;
+}
+
+/**
+ * Apply replacements matched against `baseContent` to `originalContent` while
+ * preserving unchanged line blocks from the original.
+ *
+ * This is useful when `baseContent` is a normalized view of the original. Each
+ * replacement is widened to the lines it actually touches, those touched lines
+ * are rewritten from the normalized base, and all other lines are copied back
+ * from `originalContent`. The actual replacement ranges drive preservation so
+ * duplicate normalized lines cannot be aligned to the wrong occurrence.
+ */
+export function applyReplacementsPreservingUnchangedLines(
+ originalContent: string,
+ baseContent: string,
+ replacements: TextReplacement[],
+): string {
+ const originalLines = splitLinesWithEndings(originalContent);
+ const baseLines = getLineSpans(baseContent);
+ if (originalLines.length !== baseLines.length) {
+ throw new Error("Cannot preserve unchanged lines because the base content has a different line count.");
+ }
+
+ const groups: Array<{ startLine: number; endLine: number; replacements: TextReplacement[] }> = [];
+ const sortedReplacements = [...replacements].sort((a, b) => a.matchIndex - b.matchIndex);
+ for (const replacement of sortedReplacements) {
+ const range = getReplacementLineRange(baseLines, replacement);
+ const current = groups[groups.length - 1];
+ if (current && range.startLine < current.endLine) {
+ current.endLine = Math.max(current.endLine, range.endLine);
+ current.replacements.push(replacement);
+ continue;
+ }
+ groups.push({ ...range, replacements: [replacement] });
+ }
+
+ let originalLineIndex = 0;
+ let result = "";
+ for (const group of groups) {
+ result += originalLines.slice(originalLineIndex, group.startLine).join("");
+
+ const groupStartOffset = baseLines[group.startLine].start;
+ const groupEndOffset = baseLines[group.endLine - 1].end;
+ result += applyReplacements(
+ baseContent.slice(groupStartOffset, groupEndOffset),
+ group.replacements,
+ groupStartOffset,
+ );
+ originalLineIndex = group.endLine;
+ }
+ result += originalLines.slice(originalLineIndex).join("");
+
+ return result;
+}
+
export interface FuzzyMatchResult {
/** Whether a match was found */
found: boolean;
@@ -75,13 +192,6 @@ export interface Edit {
newText: string;
}
-interface MatchedEdit {
- editIndex: number;
- matchIndex: number;
- matchLength: number;
- newText: string;
-}
-
export interface AppliedEditsResult {
baseContent: string;
newContent: string;
@@ -121,9 +231,9 @@ export function fuzzyFindText(content: string, oldText: string): FuzzyMatchResul
};
}
- // When fuzzy matching, we work in the normalized space for replacement.
- // This means the output will have normalized whitespace/quotes/dashes,
- // which is acceptable since we're fixing minor formatting differences anyway.
+ // When fuzzy matching, return offsets in normalized space. Callers can use
+ // the normalized content to compute replacements, then decide how much of
+ // that normalized output should be written back.
return {
found: true,
index: fuzzyIndex,
@@ -187,8 +297,9 @@ function getNoChangeError(path: string, totalEdits: number): Error {
*
* All edits are matched against the same original content. Replacements are
* then applied in reverse order so offsets remain stable. If any edit needs
- * fuzzy matching, the operation runs in fuzzy-normalized content space to
- * preserve current single-edit behavior.
+ * fuzzy matching, the operation runs in fuzzy-normalized content space and then
+ * overlays those line-level changes onto the original content so unchanged line
+ * blocks keep their original bytes.
*/
export function applyEditsToNormalizedContent(
normalizedContent: string,
@@ -207,19 +318,18 @@ export function applyEditsToNormalizedContent(
}
const initialMatches = normalizedEdits.map((edit) => fuzzyFindText(normalizedContent, edit.oldText));
- const baseContent = initialMatches.some((match) => match.usedFuzzyMatch)
- ? normalizeForFuzzyMatch(normalizedContent)
- : normalizedContent;
+ const usedFuzzyMatch = initialMatches.some((match) => match.usedFuzzyMatch);
+ const replacementBaseContent = usedFuzzyMatch ? normalizeForFuzzyMatch(normalizedContent) : normalizedContent;
const matchedEdits: MatchedEdit[] = [];
for (let i = 0; i < normalizedEdits.length; i++) {
const edit = normalizedEdits[i];
- const matchResult = fuzzyFindText(baseContent, edit.oldText);
+ const matchResult = fuzzyFindText(replacementBaseContent, edit.oldText);
if (!matchResult.found) {
throw getNotFoundError(path, i, normalizedEdits.length);
}
- const occurrences = countOccurrences(baseContent, edit.oldText);
+ const occurrences = countOccurrences(replacementBaseContent, edit.oldText);
if (occurrences > 1) {
throw getDuplicateError(path, i, normalizedEdits.length, occurrences);
}
@@ -243,14 +353,10 @@ export function applyEditsToNormalizedContent(
}
}
- let newContent = baseContent;
- for (let i = matchedEdits.length - 1; i >= 0; i--) {
- const edit = matchedEdits[i];
- newContent =
- newContent.substring(0, edit.matchIndex) +
- edit.newText +
- newContent.substring(edit.matchIndex + edit.matchLength);
- }
+ const baseContent = normalizedContent;
+ const newContent = usedFuzzyMatch
+ ? applyReplacementsPreservingUnchangedLines(normalizedContent, replacementBaseContent, matchedEdits)
+ : applyReplacements(replacementBaseContent, matchedEdits);
if (baseContent === newContent) {
throw getNoChangeError(path, normalizedEdits.length);
diff --git a/packages/coding-agent/src/core/tools/find.ts b/packages/coding-agent/src/core/tools/find.ts
index 6f852f61..e03b728a 100644
--- a/packages/coding-agent/src/core/tools/find.ts
+++ b/packages/coding-agent/src/core/tools/find.ts
@@ -221,17 +221,24 @@ export function createFindToolDefinition(
return;
}
- // Build fd arguments. --no-require-git makes fd apply hierarchical .gitignore
- // semantics whether or not the search path is inside a git repository, without
- // leaking sibling-directory rules the way --ignore-file (a global source) would.
- const args: string[] = [
- "--glob",
- "--color=never",
- "--hidden",
- "--no-require-git",
- "--max-results",
- String(effectiveLimit),
- ];
+ const args: string[] = ["--glob", "--color=never", "--hidden"];
+
+ // fd normally ignores .gitignore outside git repos, so keep --no-require-git
+ // there. Inside repos, use fd's default git-aware behavior so parent
+ // .gitignore rules stop at nested repo boundaries:
+ // https://github.com/earendil-works/pi/issues/5960
+ let insideGitRepo = false;
+ for (let current = searchPath; ; ) {
+ if (await pathExists(path.join(current, ".git"))) {
+ insideGitRepo = true;
+ break;
+ }
+ const parent = path.dirname(current);
+ if (parent === current) break;
+ current = parent;
+ }
+ if (!insideGitRepo) args.push("--no-require-git");
+ args.push("--max-results", String(effectiveLimit));
// fd --glob matches against the basename unless --full-path is set; in --full-path
// mode it matches against the absolute candidate path, so a path-containing
diff --git a/packages/coding-agent/src/core/trust-manager.ts b/packages/coding-agent/src/core/trust-manager.ts
index 69f616ae..9c494b47 100644
--- a/packages/coding-agent/src/core/trust-manager.ts
+++ b/packages/coding-agent/src/core/trust-manager.ts
@@ -1,4 +1,5 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
+import { homedir } from "node:os";
import { dirname, join } from "node:path";
import lockfile from "proper-lockfile";
import { CONFIG_DIR_NAME } from "../config.ts";
@@ -25,6 +26,16 @@ export interface ProjectTrustOption {
type TrustFile = Record;
+const TRUST_REQUIRING_PROJECT_CONFIG_RESOURCES = [
+ "settings.json",
+ "extensions",
+ "skills",
+ "prompts",
+ "themes",
+ "SYSTEM.md",
+ "APPEND_SYSTEM.md",
+] as const;
+
function normalizeCwd(cwd: string): string {
return canonicalizePath(resolvePath(cwd));
}
@@ -45,18 +56,14 @@ function findNearestTrustEntry(data: TrustFile, cwd: string): ProjectTrustStoreE
}
}
-export function getProjectTrustPath(cwd: string): string {
- return normalizeCwd(cwd);
-}
-
export function getProjectTrustParentPath(cwd: string): string | undefined {
- const trustPath = getProjectTrustPath(cwd);
+ const trustPath = normalizeCwd(cwd);
const parentDir = dirname(trustPath);
return parentDir === trustPath ? undefined : parentDir;
}
export function getProjectTrustOptions(cwd: string, options?: { includeSessionOnly?: boolean }): ProjectTrustOption[] {
- const trustPath = getProjectTrustPath(cwd);
+ const trustPath = normalizeCwd(cwd);
const trustOptions: ProjectTrustOption[] = [
{ label: "Trust", trusted: true, updates: [{ path: trustPath, decision: true }], savedPath: trustPath },
];
@@ -167,18 +174,26 @@ function withTrustFileLock(path: string, fn: () => T): T {
}
}
-export function hasProjectConfigDir(cwd: string): boolean {
- return existsSync(join(canonicalizePath(resolvePath(cwd)), CONFIG_DIR_NAME));
-}
-
-export function hasProjectTrustInputs(cwd: string): boolean {
+/**
+ * Returns true when cwd has project-local resources that must be gated by
+ * project trust: trust-requiring entries under cwd/.pi, or .agents/skills in
+ * cwd or one of its ancestors. Returns false when no such project resources
+ * exist. The user/global ~/.agents/skills directory is always treated as a
+ * trusted user resource and is ignored here, even when cwd is $HOME.
+ */
+export function hasTrustRequiringProjectResources(cwd: string): boolean {
+ const homeDir = canonicalizePath(resolvePath(process.env.HOME || homedir()));
+ const userAgentsSkillsDir = join(homeDir, ".agents", "skills");
let currentDir = canonicalizePath(resolvePath(cwd));
- if (hasProjectConfigDir(currentDir)) {
+
+ const configDir = join(currentDir, CONFIG_DIR_NAME);
+ if (TRUST_REQUIRING_PROJECT_CONFIG_RESOURCES.some((entry) => existsSync(join(configDir, entry)))) {
return true;
}
while (true) {
- if (existsSync(join(currentDir, ".agents", "skills"))) {
+ const agentsSkillsDir = join(currentDir, ".agents", "skills");
+ if (agentsSkillsDir !== userAgentsSkillsDir && existsSync(agentsSkillsDir)) {
return true;
}
diff --git a/packages/coding-agent/src/index.ts b/packages/coding-agent/src/index.ts
index 958c7ebb..5830ecda 100644
--- a/packages/coding-agent/src/index.ts
+++ b/packages/coding-agent/src/index.ts
@@ -3,7 +3,15 @@
export { type Args, parseArgs } from "./cli/args.ts";
// Config paths
-export { getAgentDir, getDocsPath, getExamplesPath, getPackageDir, getReadmePath, VERSION } from "./config.ts";
+export {
+ CONFIG_DIR_NAME,
+ getAgentDir,
+ getDocsPath,
+ getExamplesPath,
+ getPackageDir,
+ getReadmePath,
+ VERSION,
+} from "./config.ts";
export {
AgentSession,
type AgentSessionConfig,
@@ -238,6 +246,7 @@ export {
type SkillFrontmatter,
} from "./core/skills.ts";
export { createSyntheticSourceInfo } from "./core/source-info.ts";
+export { type EditDiffResult, generateDiffString, generateUnifiedPatch } from "./core/tools/edit-diff.ts";
// Tools
export {
type BashOperations,
@@ -289,7 +298,7 @@ export {
withFileMutationQueue,
} from "./core/tools/index.ts";
export {
- hasProjectTrustInputs,
+ hasTrustRequiringProjectResources,
type ProjectTrustDecision,
ProjectTrustStore,
type ProjectTrustStoreEntry,
diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts
index 0bc24685..f66040bb 100644
--- a/packages/coding-agent/src/main.ts
+++ b/packages/coding-agent/src/main.ts
@@ -26,7 +26,7 @@ import { formatNoModelsAvailableMessage } from "./core/auth-guidance.ts";
import { AuthStorage } from "./core/auth-storage.ts";
import { exportFromFile } from "./core/export-html/index.ts";
import type { ExtensionFactory } from "./core/extensions/types.ts";
-import { configureHttpDispatcher } from "./core/http-dispatcher.ts";
+import { applyHttpProxySettings, configureHttpDispatcher } from "./core/http-dispatcher.ts";
import type { ModelRegistry } from "./core/model-registry.ts";
import { resolveCliModel, resolveModelScope, type ScopedModel } from "./core/model-resolver.ts";
import { restoreStdout, takeOverStdout } from "./core/output-guard.ts";
@@ -41,7 +41,7 @@ import {
import { assertValidSessionId, SessionManager } from "./core/session-manager.ts";
import { SettingsManager } from "./core/settings-manager.ts";
import { printTimings, resetTimings, time } from "./core/timings.ts";
-import { hasProjectTrustInputs, ProjectTrustStore } from "./core/trust-manager.ts";
+import { hasTrustRequiringProjectResources, ProjectTrustStore } from "./core/trust-manager.ts";
import { runMigrations, showDeprecationWarnings } from "./migrations.ts";
import { InteractiveMode, runPrintMode, runRpcMode } from "./modes/index.ts";
import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme.ts";
@@ -466,7 +466,22 @@ export async function main(args: string[], options?: MainOptions) {
cleanupWindowsSelfUpdateQuarantine(getPackageDir());
}
+ const cwd = process.cwd();
+ const agentDir = getAgentDir();
+ const bootstrapSettingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted: false });
+ applyHttpProxySettings(bootstrapSettingsManager.getGlobalSettings().httpProxy);
+ configureHttpDispatcher();
+
if (await handlePackageCommand(args, { extensionFactories: options?.extensionFactories })) {
+ const exitCode = process.exitCode ?? 0;
+ if (process.platform === "win32" && exitCode === 0 && args[0] === "update") {
+ // We normally prefer process.exit(0) for package commands so bad extensions cannot keep
+ // one-shot commands alive. On Windows, Node can assert after fetch() if process.exit(0)
+ // runs during teardown; let successful `pi update` drain naturally instead.
+ // https://github.com/nodejs/node/issues/56645
+ return;
+ }
+ process.exit(exitCode);
return;
}
@@ -520,11 +535,9 @@ export async function main(args: string[], options?: MainOptions) {
validateSessionIdFlags(parsed);
// Run migrations (pass cwd for project-local migrations)
- const { migratedAuthProviders: migratedProviders, deprecationWarnings } = runMigrations(process.cwd());
+ const { migratedAuthProviders: migratedProviders, deprecationWarnings } = runMigrations(cwd);
time("runMigrations");
- const cwd = process.cwd();
- const agentDir = getAgentDir();
const startupSettingsManager = SettingsManager.create(cwd, agentDir);
reportDiagnostics(collectSettingsDiagnostics(startupSettingsManager, "startup session lookup"));
@@ -572,7 +585,9 @@ export async function main(args: string[], options?: MainOptions) {
const trustStore = new ProjectTrustStore(agentDir);
const sessionCwd = sessionManager.getCwd();
const autoTrustOnReloadCwd =
- parsed.projectTrustOverride === undefined && !hasProjectTrustInputs(sessionCwd) ? sessionCwd : undefined;
+ parsed.projectTrustOverride === undefined && !hasTrustRequiringProjectResources(sessionCwd)
+ ? sessionCwd
+ : undefined;
const trustPromptMode: AppMode = parsed.help || parsed.listModels !== undefined ? "print" : appMode;
const projectTrustByCwd = new Map();
@@ -591,12 +606,14 @@ export async function main(args: string[], options?: MainOptions) {
const isInitialRuntime = sessionStartEvent === undefined;
const projectTrustDiagnostics: AgentSessionRuntimeDiagnostic[] = [];
const cachedProjectTrust = projectTrustByCwd.get(cwd);
- const hasTrustInputs = hasProjectTrustInputs(cwd);
+ const hasTrustRequiringResources = hasTrustRequiringProjectResources(cwd);
const shouldResolveProjectTrust =
- parsed.projectTrustOverride === undefined && cachedProjectTrust === undefined && hasTrustInputs;
+ parsed.projectTrustOverride === undefined && cachedProjectTrust === undefined && hasTrustRequiringResources;
const projectTrusted = shouldResolveProjectTrust
? false
- : (cachedProjectTrust ?? parsed.projectTrustOverride ?? (!hasTrustInputs || trustStore.get(cwd) === true));
+ : (cachedProjectTrust ??
+ parsed.projectTrustOverride ??
+ (!hasTrustRequiringResources || trustStore.get(cwd) === true));
const runtimeSettingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted });
const services = await createAgentSessionServices({
cwd,
@@ -713,6 +730,7 @@ export async function main(args: string[], options?: MainOptions) {
time("createAgentSessionRuntime");
const { services, session, modelFallbackMessage } = runtime;
const { settingsManager, modelRegistry, resourceLoader } = services;
+ applyHttpProxySettings(settingsManager.getGlobalSettings().httpProxy);
configureHttpDispatcher(settingsManager.getHttpIdleTimeoutMs());
if (parsed.help) {
diff --git a/packages/coding-agent/src/migrations.ts b/packages/coding-agent/src/migrations.ts
index 5cce43b8..39aeea04 100644
--- a/packages/coding-agent/src/migrations.ts
+++ b/packages/coding-agent/src/migrations.ts
@@ -3,12 +3,10 @@
*/
import chalk from "chalk";
-import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
+import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
import { dirname, join } from "path";
import { CONFIG_DIR_NAME, getAgentDir, getBinDir } from "./config.ts";
import { migrateKeybindingsConfig } from "./core/keybindings.ts";
-import { isLegacyEnvVarNameConfigValue } from "./core/resolve-config-value.ts";
-import { stripJsonComments } from "./utils/json.ts";
const MIGRATION_GUIDE_URL =
"https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/CHANGELOG.md#extensions-migration";
@@ -74,140 +72,6 @@ export function migrateAuthToAuthJson(): string[] {
return providers;
}
-interface ConfigValueMigration {
- location: string;
- from: string;
- to: string;
-}
-
-function migrateLegacyEnvVarString(value: string): string | undefined {
- return isLegacyEnvVarNameConfigValue(value) ? `$${value}` : undefined;
-}
-
-function migrateStringProperty(
- record: Record,
- key: string,
- location: string,
- migrations: ConfigValueMigration[],
-): boolean {
- const value = record[key];
- if (typeof value !== "string") return false;
- const migrated = migrateLegacyEnvVarString(value);
- if (migrated === undefined) return false;
- record[key] = migrated;
- migrations.push({ location, from: value, to: migrated });
- return true;
-}
-
-function migrateHeadersConfig(headers: unknown, location: string, migrations: ConfigValueMigration[]): boolean {
- if (typeof headers !== "object" || headers === null || Array.isArray(headers)) return false;
- const headerRecord = headers as Record;
- let migrated = false;
- for (const [key, value] of Object.entries(headerRecord)) {
- if (typeof value !== "string") continue;
- const migratedValue = migrateLegacyEnvVarString(value);
- if (migratedValue === undefined) continue;
- headerRecord[key] = migratedValue;
- migrations.push({ location: `${location}[${JSON.stringify(key)}]`, from: value, to: migratedValue });
- migrated = true;
- }
- return migrated;
-}
-
-function migrateAuthJsonConfigValues(agentDir: string): ConfigValueMigration[] {
- const authPath = join(agentDir, "auth.json");
- if (!existsSync(authPath)) return [];
-
- try {
- const parsed = JSON.parse(readFileSync(authPath, "utf-8")) as unknown;
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return [];
- const authData = parsed as Record;
-
- const migrations: ConfigValueMigration[] = [];
- for (const [provider, credential] of Object.entries(authData)) {
- if (typeof credential !== "object" || credential === null || Array.isArray(credential)) continue;
- const credentialRecord = credential as Record;
- if (credentialRecord.type !== "api_key") continue;
- migrateStringProperty(credentialRecord, "key", `auth.json[${JSON.stringify(provider)}].key`, migrations);
- }
-
- if (migrations.length === 0) return [];
- writeFileSync(authPath, `${JSON.stringify(parsed, null, 2)}\n`, "utf-8");
- chmodSync(authPath, 0o600);
- return migrations;
- } catch {
- return [];
- }
-}
-
-function migrateModelsJsonConfigValues(agentDir: string): ConfigValueMigration[] {
- const modelsPath = join(agentDir, "models.json");
- if (!existsSync(modelsPath)) return [];
-
- try {
- const parsed = JSON.parse(stripJsonComments(readFileSync(modelsPath, "utf-8"))) as unknown;
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return [];
- const modelsData = parsed as Record;
- const providers = modelsData.providers;
- if (typeof providers !== "object" || providers === null || Array.isArray(providers)) return [];
-
- const migrations: ConfigValueMigration[] = [];
- for (const [provider, providerConfig] of Object.entries(providers)) {
- if (typeof providerConfig !== "object" || providerConfig === null || Array.isArray(providerConfig)) continue;
- const providerRecord = providerConfig as Record;
- const providerLocation = `models.json.providers[${JSON.stringify(provider)}]`;
- migrateStringProperty(providerRecord, "apiKey", `${providerLocation}.apiKey`, migrations);
- migrateHeadersConfig(providerRecord.headers, `${providerLocation}.headers`, migrations);
-
- if (Array.isArray(providerRecord.models)) {
- for (let index = 0; index < providerRecord.models.length; index++) {
- const modelConfig = providerRecord.models[index];
- if (typeof modelConfig !== "object" || modelConfig === null || Array.isArray(modelConfig)) continue;
- const modelRecord = modelConfig as Record;
- const modelKey = typeof modelRecord.id === "string" ? JSON.stringify(modelRecord.id) : String(index);
- migrateHeadersConfig(modelRecord.headers, `${providerLocation}.models[${modelKey}].headers`, migrations);
- }
- }
-
- const modelOverrides = providerRecord.modelOverrides;
- if (typeof modelOverrides === "object" && modelOverrides !== null && !Array.isArray(modelOverrides)) {
- for (const [modelId, modelOverride] of Object.entries(modelOverrides)) {
- if (typeof modelOverride !== "object" || modelOverride === null || Array.isArray(modelOverride))
- continue;
- const modelOverrideRecord = modelOverride as Record;
- migrateHeadersConfig(
- modelOverrideRecord.headers,
- `${providerLocation}.modelOverrides[${JSON.stringify(modelId)}].headers`,
- migrations,
- );
- }
- }
- }
-
- if (migrations.length === 0) return [];
- writeFileSync(modelsPath, `${JSON.stringify(parsed, null, 2)}\n`, "utf-8");
- return migrations;
- } catch {
- return [];
- }
-}
-
-function migrateExplicitEnvVarConfigValues(): void {
- const agentDir = getAgentDir();
- const migrations = [...migrateAuthJsonConfigValues(agentDir), ...migrateModelsJsonConfigValues(agentDir)];
- if (migrations.length === 0) return;
-
- const details = migrations.map((migration) => ` - ${migration.location}: ${migration.from} -> ${migration.to}`);
- console.log(
- chalk.yellow(
- [
- "Warning: Migrated API key/header environment references to explicit $ENV_VAR syntax. Plain strings will be treated as literals.",
- ...details,
- ].join("\n"),
- ),
- );
-}
-
/**
* Migrate sessions from ~/.pi/agent/*.jsonl to proper session directories.
*
@@ -443,7 +307,6 @@ export function runMigrations(cwd: string): {
deprecationWarnings: string[];
} {
const migratedAuthProviders = migrateAuthToAuthJson();
- migrateExplicitEnvVarConfigValues();
migrateSessionsFromAgentRoot();
migrateToolsToBin();
migrateKeybindingsConfigFile();
diff --git a/packages/coding-agent/src/modes/interactive/components/config-selector.ts b/packages/coding-agent/src/modes/interactive/components/config-selector.ts
index 93ef4bea..7c46841d 100644
--- a/packages/coding-agent/src/modes/interactive/components/config-selector.ts
+++ b/packages/coding-agent/src/modes/interactive/components/config-selector.ts
@@ -73,7 +73,7 @@ function formatBaseDir(baseDir: string): string {
return displayPath.endsWith("/") ? displayPath : `${displayPath}/`;
}
-function getGroupLabel(metadata: PathMetadata): string {
+function getGroupLabel(metadata: PathMetadata, agentDir: string): string {
if (metadata.origin === "package") {
return `${metadata.source} (${metadata.scope})`;
}
@@ -84,12 +84,12 @@ function getGroupLabel(metadata: PathMetadata): string {
? `User (${formatBaseDir(metadata.baseDir)})`
: `Project (${formatBaseDir(metadata.baseDir)})`;
}
- return metadata.scope === "user" ? "User (~/.pi/agent/)" : "Project (.pi/)";
+ return metadata.scope === "user" ? `User (${formatBaseDir(agentDir)})` : `Project (${CONFIG_DIR_NAME}/)`;
}
return metadata.scope === "user" ? "User settings" : "Project settings";
}
-function buildGroups(resolved: ResolvedPaths): ResourceGroup[] {
+function buildGroups(resolved: ResolvedPaths, agentDir: string): ResourceGroup[] {
const groupMap = new Map();
const addToGroup = (resources: ResolvedResource[], resourceType: ResourceType) => {
@@ -100,7 +100,7 @@ function buildGroups(resolved: ResolvedPaths): ResourceGroup[] {
if (!groupMap.has(groupKey)) {
groupMap.set(groupKey, {
key: groupKey,
- label: getGroupLabel(metadata),
+ label: getGroupLabel(metadata, agentDir),
scope: metadata.scope,
origin: metadata.origin,
source: metadata.source,
@@ -601,7 +601,7 @@ export class ConfigSelectorComponent extends Container implements Focusable {
) {
super();
- const groups = buildGroups(resolvedPaths);
+ const groups = buildGroups(resolvedPaths, agentDir);
// Add header
this.addChild(new Spacer(1));
diff --git a/packages/coding-agent/src/modes/interactive/components/footer.ts b/packages/coding-agent/src/modes/interactive/components/footer.ts
index f108ae5e..aa537127 100644
--- a/packages/coding-agent/src/modes/interactive/components/footer.ts
+++ b/packages/coding-agent/src/modes/interactive/components/footer.ts
@@ -1,6 +1,7 @@
import { isAbsolute, relative, resolve, sep } from "node:path";
import { type Component, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
import type { AgentSession } from "../../../core/agent-session.ts";
+import { areExperimentalFeaturesEnabled } from "../../../core/experimental.ts";
import type { ReadonlyFooterDataProvider } from "../../../core/footer-data-provider.ts";
import { theme } from "../theme/theme.ts";
@@ -159,6 +160,9 @@ export class FooterComponent implements Component {
contextPercentStr = contextPercentDisplay;
}
statsParts.push(contextPercentStr);
+ if (areExperimentalFeaturesEnabled()) {
+ statsParts.push(`${theme.fg("dim", "•")} ${theme.bold(theme.fg("warning", "xp"))}`);
+ }
let statsLeft = statsParts.join(" ");
diff --git a/packages/coding-agent/src/modes/interactive/components/login-dialog.ts b/packages/coding-agent/src/modes/interactive/components/login-dialog.ts
index 958db6d6..3fc4b516 100644
--- a/packages/coding-agent/src/modes/interactive/components/login-dialog.ts
+++ b/packages/coding-agent/src/modes/interactive/components/login-dialog.ts
@@ -128,7 +128,6 @@ export class LoginDialogComponent extends Container implements Focusable {
this.contentContainer.addChild(new Spacer(1));
this.contentContainer.addChild(new Text(theme.fg("warning", `Enter code: ${info.userCode}`), 1, 0));
- openBrowser(info.verificationUri);
this.tui.requestRender();
}
diff --git a/packages/coding-agent/src/modes/interactive/components/model-selector.ts b/packages/coding-agent/src/modes/interactive/components/model-selector.ts
index b9f5ec77..32711929 100644
--- a/packages/coding-agent/src/modes/interactive/components/model-selector.ts
+++ b/packages/coding-agent/src/modes/interactive/components/model-selector.ts
@@ -11,6 +11,7 @@ import {
} from "@earendil-works/pi-tui";
import type { ModelRegistry } from "../../../core/model-registry.ts";
import type { SettingsManager } from "../../../core/settings-manager.ts";
+import { getModelSelectorSearchText } from "../model-search.ts";
import { theme } from "../theme/theme.ts";
import { DynamicBorder } from "./dynamic-border.ts";
import { keyHint } from "./keybinding-hints.ts";
@@ -217,10 +218,8 @@ export class ModelSelectorComponent extends Container implements Focusable {
private filterModels(query: string): void {
this.filteredModels = query
- ? fuzzyFilter(
- this.activeModels,
- query,
- ({ id, provider }) => `${id} ${provider} ${provider}/${id} ${provider} ${id}`,
+ ? fuzzyFilter(this.activeModels, query, ({ id, provider, model }) =>
+ getModelSelectorSearchText({ id, provider, name: model.name }),
)
: this.activeModels;
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredModels.length - 1));
diff --git a/packages/coding-agent/src/modes/interactive/components/scoped-models-selector.ts b/packages/coding-agent/src/modes/interactive/components/scoped-models-selector.ts
index 06ce9169..772e3af0 100644
--- a/packages/coding-agent/src/modes/interactive/components/scoped-models-selector.ts
+++ b/packages/coding-agent/src/modes/interactive/components/scoped-models-selector.ts
@@ -10,6 +10,7 @@ import {
Spacer,
Text,
} from "@earendil-works/pi-tui";
+import { getModelSearchText } from "../model-search.ts";
import { theme } from "../theme/theme.ts";
import { DynamicBorder } from "./dynamic-border.ts";
import { keyText } from "./keybinding-hints.ts";
@@ -182,7 +183,11 @@ export class ScopedModelsSelectorComponent extends Container implements Focusabl
private refresh(): void {
const query = this.searchInput.getValue();
const items = this.buildItems();
- this.filteredItems = query ? fuzzyFilter(items, query, (i) => `${i.model.id} ${i.model.provider}`) : items;
+ this.filteredItems = query
+ ? fuzzyFilter(items, query, (i) =>
+ getModelSearchText({ id: i.model.id, provider: i.model.provider, name: i.model.name }),
+ )
+ : items;
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredItems.length - 1));
this.updateList();
this.footerText.setText(this.getFooterText());
diff --git a/packages/coding-agent/src/modes/interactive/components/session-selector.ts b/packages/coding-agent/src/modes/interactive/components/session-selector.ts
index 74141e5e..a92f0762 100644
--- a/packages/coding-agent/src/modes/interactive/components/session-selector.ts
+++ b/packages/coding-agent/src/modes/interactive/components/session-selector.ts
@@ -694,7 +694,6 @@ export class SessionSelectorComponent extends Container implements Focusable {
private allSessions: SessionInfo[] | null = null;
private currentSessionsLoader: SessionsLoader;
private allSessionsLoader: SessionsLoader;
- private onCancel: () => void;
private requestRender: () => void;
private renameSession?: (sessionPath: string, currentName: string | undefined) => Promise;
private currentLoading = false;
@@ -751,7 +750,6 @@ export class SessionSelectorComponent extends Container implements Focusable {
this.keybindings = options?.keybindings ?? KeybindingsManager.create();
this.currentSessionsLoader = currentSessionsLoader;
this.allSessionsLoader = allSessionsLoader;
- this.onCancel = onCancel;
this.requestRender = requestRender;
this.header = new SessionSelectorHeader(this.scope, this.sortMode, this.nameFilter, this.requestRender);
const renameSession = options?.renameSession;
@@ -948,10 +946,6 @@ export class SessionSelectorComponent extends Container implements Focusable {
this.header.setLoading(false);
this.sessionList.setSessions(sessions, showCwd);
this.requestRender();
-
- if (scope === "all" && sessions.length === 0 && (this.currentSessions?.length ?? 0) === 0) {
- this.onCancel();
- }
} catch (err) {
if (scope === "current") {
this.currentLoading = false;
diff --git a/packages/coding-agent/src/modes/interactive/components/settings-selector.ts b/packages/coding-agent/src/modes/interactive/components/settings-selector.ts
index 39d25f80..7cc92614 100644
--- a/packages/coding-agent/src/modes/interactive/components/settings-selector.ts
+++ b/packages/coding-agent/src/modes/interactive/components/settings-selector.ts
@@ -1,6 +1,7 @@
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
import type { Transport } from "@earendil-works/pi-ai";
import {
+ type Component,
Container,
getCapabilities,
type SelectItem,
@@ -13,7 +14,13 @@ import {
} from "@earendil-works/pi-tui";
import { formatHttpIdleTimeoutMs, HTTP_IDLE_TIMEOUT_CHOICES } from "../../../core/http-dispatcher.ts";
import type { DefaultProjectTrust, WarningSettings } from "../../../core/settings-manager.ts";
-import { getSelectListTheme, getSettingsListTheme, theme } from "../theme/theme.ts";
+import {
+ getSelectListTheme,
+ getSettingsListTheme,
+ parseAutoThemeSetting,
+ type TerminalTheme,
+ theme,
+} from "../theme/theme.ts";
import { DynamicBorder } from "./dynamic-border.ts";
import { keyDisplayText } from "./keybinding-hints.ts";
@@ -55,6 +62,7 @@ export interface SettingsConfig {
thinkingLevel: ThinkingLevel;
availableThinkingLevels: ThinkingLevel[];
currentTheme: string;
+ terminalTheme: TerminalTheme;
availableThemes: string[];
hideThinkingBlock: boolean;
collapseChangelog: boolean;
@@ -210,6 +218,249 @@ class SelectSubmenu extends Container {
}
}
+function themeItems(availableThemes: string[]): SelectItem[] {
+ return availableThemes.map((name) => ({ value: name, label: name }));
+}
+
+const AUTOMATIC_THEME_VALUE = "/";
+
+function singleModeThemeItems(availableThemes: string[]): SelectItem[] {
+ return [
+ {
+ value: AUTOMATIC_THEME_VALUE,
+ label: "Automatic",
+ description: "Use separate themes for light and dark terminal appearance",
+ },
+ ...themeItems(availableThemes),
+ ];
+}
+
+function preferredTheme(availableThemes: string[], preferred: string | undefined, fallback: string): string {
+ if (preferred && availableThemes.includes(preferred)) return preferred;
+ if (availableThemes.includes(fallback)) return fallback;
+ return availableThemes[0] ?? fallback;
+}
+
+function defaultAutomaticThemes(
+ currentThemeSetting: string,
+ availableThemes: string[],
+): { lightTheme: string; darkTheme: string } {
+ const autoTheme = parseAutoThemeSetting(currentThemeSetting);
+ if (autoTheme) return autoTheme;
+
+ const currentFixedTheme = currentThemeSetting.includes("/") ? undefined : currentThemeSetting;
+ const themeName = preferredTheme(availableThemes, currentFixedTheme, "dark");
+ return { lightTheme: themeName, darkTheme: themeName };
+}
+
+class ThemeSubmenu extends Container {
+ private inputComponent: Component | undefined;
+ private readonly callbacks: SettingsCallbacks;
+ private readonly availableThemes: string[];
+ private readonly terminalTheme: TerminalTheme;
+ private readonly onDone: (selectedValue?: string) => void;
+ private readonly originalThemeSetting: string;
+ private mode: "single" | "automatic";
+ private singleTheme: string;
+ private lightTheme: string;
+ private darkTheme: string;
+
+ constructor(
+ currentThemeSetting: string,
+ terminalTheme: TerminalTheme,
+ availableThemes: string[],
+ callbacks: SettingsCallbacks,
+ onDone: (selectedValue?: string) => void,
+ ) {
+ super();
+ this.callbacks = callbacks;
+ this.availableThemes = availableThemes;
+ this.terminalTheme = terminalTheme;
+ this.onDone = onDone;
+ this.originalThemeSetting = currentThemeSetting;
+ const autoTheme = parseAutoThemeSetting(currentThemeSetting);
+ const automaticThemes = defaultAutomaticThemes(currentThemeSetting, availableThemes);
+ const fixedTheme = autoTheme || currentThemeSetting.includes("/") ? undefined : currentThemeSetting;
+ this.mode = autoTheme ? "automatic" : "single";
+ this.lightTheme = automaticThemes.lightTheme;
+ this.darkTheme = automaticThemes.darkTheme;
+ this.singleTheme = preferredTheme(
+ availableThemes,
+ fixedTheme ?? (autoTheme ? this.getActiveAutomaticTheme() : undefined),
+ "dark",
+ );
+
+ if (this.mode === "automatic") {
+ this.showAutomaticMenu();
+ } else {
+ this.showSingleMenu();
+ }
+ }
+
+ handleInput(data: string): void {
+ this.inputComponent?.handleInput?.(data);
+ }
+
+ private setContent(renderComponent: Component, inputComponent: Component = renderComponent): void {
+ this.clear();
+ this.addChild(renderComponent);
+ this.inputComponent = inputComponent;
+ }
+
+ private showSingleMenu(): void {
+ this.mode = "single";
+ const menu = new SelectSubmenu(
+ "Theme",
+ "Select a theme, or choose Automatic to follow terminal appearance.",
+ singleModeThemeItems(this.availableThemes),
+ this.singleTheme,
+ (value) => {
+ if (value === AUTOMATIC_THEME_VALUE) {
+ this.mode = "automatic";
+ this.callbacks.onThemePreview?.(this.getThemeSetting());
+ this.showAutomaticMenu();
+ return;
+ }
+
+ this.singleTheme = value;
+ this.apply(value);
+ },
+ () => this.cancel(),
+ (value) => {
+ this.callbacks.onThemePreview?.(value === AUTOMATIC_THEME_VALUE ? this.getAutomaticThemeSetting() : value);
+ },
+ );
+ this.setContent(menu);
+ }
+
+ private showAutomaticMenu(): void {
+ this.mode = "automatic";
+ const content = new Container();
+ content.addChild(new Text(theme.bold(theme.fg("accent", "Automatic Theme")), 0, 0));
+ content.addChild(new Spacer(1));
+ content.addChild(new Text(theme.fg("muted", "Choose themes for terminal light and dark appearance."), 0, 0));
+ content.addChild(new Text(theme.fg("muted", "Light/dark detection requires terminal support."), 0, 0));
+ content.addChild(new Spacer(1));
+
+ const items: SettingItem[] = [
+ {
+ id: "light-theme",
+ label: "Light theme",
+ description: "Theme to use in automatic mode when the terminal is light",
+ currentValue: this.lightTheme,
+ submenu: (currentValue, done) =>
+ this.createThemeSelect(
+ "Light Theme",
+ "Select the theme to use for light terminal appearance",
+ currentValue,
+ done,
+ (value) => {
+ this.lightTheme = value;
+ this.callbacks.onThemePreview?.(this.getThemeSetting());
+ done(value);
+ },
+ ),
+ },
+ {
+ id: "dark-theme",
+ label: "Dark theme",
+ description: "Theme to use in automatic mode when the terminal is dark",
+ currentValue: this.darkTheme,
+ submenu: (currentValue, done) =>
+ this.createThemeSelect(
+ "Dark Theme",
+ "Select the theme to use for dark terminal appearance",
+ currentValue,
+ done,
+ (value) => {
+ this.darkTheme = value;
+ this.callbacks.onThemePreview?.(this.getThemeSetting());
+ done(value);
+ },
+ ),
+ },
+ {
+ id: "apply",
+ label: "Apply",
+ description: "Save and go back",
+ currentValue: "save and go back",
+ values: ["save and go back"],
+ },
+ {
+ id: "single-mode",
+ label: "Change mode",
+ description: "Switch to one theme for light and dark",
+ currentValue: "switch to single theme",
+ values: ["switch to single theme"],
+ },
+ ];
+
+ const settingsList = new SettingsList(
+ items,
+ Math.min(items.length, 10),
+ getSettingsListTheme(),
+ (id) => {
+ switch (id) {
+ case "single-mode":
+ this.mode = "single";
+ this.singleTheme = this.getActiveAutomaticTheme();
+ this.callbacks.onThemePreview?.(this.singleTheme);
+ this.showSingleMenu();
+ break;
+ case "apply":
+ this.apply(this.getAutomaticThemeSetting());
+ break;
+ }
+ },
+ () => this.cancel(),
+ );
+ content.addChild(settingsList);
+ this.setContent(content, settingsList);
+ }
+
+ private createThemeSelect(
+ title: string,
+ description: string,
+ currentValue: string,
+ done: (selectedValue?: string) => void,
+ onSelect: (value: string) => void,
+ ): SelectSubmenu {
+ return new SelectSubmenu(
+ title,
+ description,
+ themeItems(this.availableThemes),
+ currentValue,
+ onSelect,
+ () => {
+ this.callbacks.onThemePreview?.(this.getThemeSetting());
+ done();
+ },
+ (value) => this.callbacks.onThemePreview?.(value),
+ );
+ }
+
+ private getThemeSetting(): string {
+ return this.mode === "automatic" ? this.getAutomaticThemeSetting() : this.singleTheme;
+ }
+
+ private getActiveAutomaticTheme(): string {
+ return this.terminalTheme === "light" ? this.lightTheme : this.darkTheme;
+ }
+
+ private getAutomaticThemeSetting(): string {
+ return `${this.lightTheme}/${this.darkTheme}`;
+ }
+
+ private apply(themeSetting: string): void {
+ this.onDone(themeSetting);
+ }
+
+ private cancel(): void {
+ this.callbacks.onThemePreview?.(this.originalThemeSetting);
+ this.onDone();
+ }
+}
+
/**
* Main settings selector component.
*/
@@ -353,28 +604,7 @@ export class SettingsSelectorComponent extends Container {
description: "Color theme for the interface",
currentValue: config.currentTheme,
submenu: (currentValue, done) =>
- new SelectSubmenu(
- "Theme",
- "Select color theme",
- config.availableThemes.map((t) => ({
- value: t,
- label: t,
- })),
- currentValue,
- (value) => {
- callbacks.onThemeChange(value);
- done(value);
- },
- () => {
- // Restore original theme on cancel
- callbacks.onThemePreview?.(currentValue);
- done();
- },
- (value) => {
- // Preview theme on selection change
- callbacks.onThemePreview?.(value);
- },
- ),
+ new ThemeSubmenu(currentValue, config.terminalTheme, config.availableThemes, callbacks, done),
},
];
@@ -561,6 +791,9 @@ export class SettingsSelectorComponent extends Container {
case "terminal-progress":
callbacks.onShowTerminalProgressChange(newValue === "true");
break;
+ case "theme":
+ callbacks.onThemeChange(newValue);
+ break;
}
},
callbacks.onCancel,
diff --git a/packages/coding-agent/src/modes/interactive/components/tree-selector.ts b/packages/coding-agent/src/modes/interactive/components/tree-selector.ts
index 990e705d..8bcdc2fb 100644
--- a/packages/coding-agent/src/modes/interactive/components/tree-selector.ts
+++ b/packages/coding-agent/src/modes/interactive/components/tree-selector.ts
@@ -4,15 +4,18 @@ import {
type Focusable,
getKeybindings,
Input,
+ type Keybinding,
Spacer,
+ sliceByColumn,
Text,
- TruncatedText,
truncateToWidth,
+ visibleWidth,
+ wrapTextWithAnsi,
} from "@earendil-works/pi-tui";
import type { SessionTreeNode } from "../../../core/session-manager.ts";
import { theme } from "../theme/theme.ts";
import { DynamicBorder } from "./dynamic-border.ts";
-import { keyHint, keyText } from "./keybinding-hints.ts";
+import { formatKeyText, keyHint } from "./keybinding-hints.ts";
/** Gutter info: position (displayIndent where connector was) and whether to show │ */
interface GutterInfo {
@@ -35,6 +38,59 @@ interface FlatNode {
isVirtualRootChild: boolean;
}
+interface HorizontalViewportRow {
+ gutter: string;
+ body: string;
+ anchorCol: number;
+ bodyWidth: number;
+ isSelected: boolean;
+}
+
+const TREE_GUTTER_WIDTH = 2;
+const MIN_VISIBLE_ANCHOR_CONTENT_WIDTH = 4;
+const MAX_VISIBLE_ANCHOR_CONTENT_WIDTH = 20;
+const MIN_ANCHOR_CONTEXT_WIDTH = 2;
+const MAX_ANCHOR_CONTEXT_WIDTH = 12;
+
+/**
+ * Render tree rows into a horizontally clipped viewport.
+ *
+ * The tree gutter is always kept visible. The row bodies are shifted left only
+ * when the selected row's anchor (the start of its entry text after tree
+ * indentation/markers) would otherwise be too far right to see useful content.
+ */
+function renderHorizontalViewport(rows: HorizontalViewportRow[], width: number): string[] {
+ const viewportWidth = Math.max(0, width - TREE_GUTTER_WIDTH);
+ const maxBodyWidth = rows.reduce((max, row) => Math.max(max, row.bodyWidth), 0);
+ const maxHorizontalScroll = Math.max(0, maxBodyWidth - viewportWidth);
+ const selectedRow = rows.find((row) => row.isSelected);
+
+ // Only pan horizontally when needed to keep enough selected-row content visible after its anchor.
+ let horizontalScroll = 0;
+ if (selectedRow && maxHorizontalScroll > 0) {
+ const minVisibleAnchorContentWidth = Math.min(
+ MAX_VISIBLE_ANCHOR_CONTENT_WIDTH,
+ Math.max(MIN_VISIBLE_ANCHOR_CONTENT_WIDTH, Math.floor(viewportWidth / 3)),
+ );
+ if (selectedRow.anchorCol > viewportWidth - minVisibleAnchorContentWidth) {
+ const anchorContextWidth = Math.min(
+ MAX_ANCHOR_CONTEXT_WIDTH,
+ Math.max(MIN_ANCHOR_CONTEXT_WIDTH, Math.floor(viewportWidth / 4)),
+ );
+ horizontalScroll = Math.min(maxHorizontalScroll, selectedRow.anchorCol - anchorContextWidth);
+ }
+ }
+
+ // Clip only the body; the fixed-width gutter remains visible as navigation context.
+ return rows.map((row) => {
+ const line =
+ horizontalScroll > 0
+ ? `${row.gutter}${sliceByColumn(row.body, horizontalScroll, viewportWidth, true)}\x1b[0m`
+ : row.gutter + row.body;
+ return truncateToWidth(line, width, "");
+ });
+}
+
/** Filter mode for tree display */
export type FilterMode = "default" | "no-tools" | "user-only" | "labeled-only" | "all";
@@ -617,6 +673,7 @@ class TreeList implements Component {
);
const endIndex = Math.min(startIndex + this.maxVisibleLines, this.filteredNodes.length);
+ const renderedRows: HorizontalViewportRow[] = [];
for (let i = startIndex; i < endIndex; i++) {
const flatNode = this.filteredNodes[i];
const entry = flatNode.node.entry;
@@ -680,14 +737,18 @@ class TreeList implements Component {
? theme.fg("muted", `${this.formatLabelTimestamp(flatNode.node.labelTimestamp)} `)
: "";
const content = this.getEntryDisplayText(flatNode.node, isSelected);
-
- let line = cursor + theme.fg("dim", prefix) + foldMarker + pathMarker + label + labelTimestamp + content;
+ const prefixPart = theme.fg("dim", prefix) + foldMarker + pathMarker;
+ const anchorCol = visibleWidth(prefixPart);
+ let gutter = cursor;
+ let body = prefixPart + label + labelTimestamp + content;
if (isSelected) {
- line = theme.bg("selectedBg", line);
+ gutter = theme.bg("selectedBg", gutter);
+ body = theme.bg("selectedBg", body);
}
- lines.push(truncateToWidth(line, width));
+ renderedRows.push({ gutter, body, anchorCol, bodyWidth: visibleWidth(body), isSelected });
}
+ lines.push(...renderHorizontalViewport(renderedRows, width));
lines.push(
truncateToWidth(
theme.fg("muted", ` (${this.selectedIndex + 1}/${this.filteredNodes.length})${this.getStatusLabels()}`),
@@ -1075,6 +1136,98 @@ class SearchLine implements Component {
handleInput(_keyData: string): void {}
}
+/** Component that renders tree help as semantic rows with chunk-aware wrapping */
+class TreeHelp implements Component {
+ invalidate(): void {}
+
+ render(width: number): string[] {
+ const items = TREE_HELP_ITEMS.map(({ keys, label, labelFirst }) => {
+ const text = formatHelpKeys(keys);
+ if (!text) return label;
+ return labelFirst ? `${label} ${text}` : `${text} ${label}`;
+ });
+
+ const availableWidth = Math.max(1, width);
+ const indent = " ";
+ const separator = " · ";
+ const lines: string[] = [];
+ let currentLine = "";
+
+ for (const item of items) {
+ const candidate = currentLine
+ ? `${currentLine}${separator}${item}`
+ : visibleWidth(`${indent}${item}`) <= availableWidth
+ ? `${indent}${item}`
+ : item;
+ if (!currentLine || visibleWidth(candidate) <= availableWidth) {
+ currentLine = candidate;
+ continue;
+ }
+
+ lines.push(...wrapTextWithAnsi(currentLine.trimEnd(), availableWidth));
+ currentLine = visibleWidth(`${indent}${item}`) <= availableWidth ? `${indent}${item}` : item;
+ }
+
+ if (currentLine) {
+ lines.push(...wrapTextWithAnsi(currentLine.trimEnd(), availableWidth));
+ }
+
+ return lines.map((line) => theme.fg("muted", line));
+ }
+}
+
+const TREE_HELP_ITEMS: Array<{ keys: Keybinding[]; label: string; labelFirst?: boolean }> = [
+ { keys: ["tui.select.up", "tui.select.down"], label: "move" },
+ { keys: ["tui.editor.cursorLeft", "tui.editor.cursorRight"], label: "page" },
+ { keys: ["app.tree.foldOrUp", "app.tree.unfoldOrDown"], label: "branch" },
+ { keys: ["app.tree.editLabel"], label: "label" },
+ { keys: ["app.tree.toggleLabelTimestamp"], label: "label time" },
+ {
+ keys: [
+ "app.tree.filter.default",
+ "app.tree.filter.noTools",
+ "app.tree.filter.userOnly",
+ "app.tree.filter.labeledOnly",
+ "app.tree.filter.all",
+ ],
+ label: "filters",
+ labelFirst: true,
+ },
+ { keys: ["app.tree.filter.cycleForward", "app.tree.filter.cycleBackward"], label: "cycle", labelFirst: true },
+];
+
+function formatHelpKeys(keybindings: Keybinding[]): string {
+ const keys: string[] = [];
+ for (const keybinding of keybindings) {
+ const key = getKeybindings().getKeys(keybinding)[0];
+ if (key !== undefined) keys.push(key);
+ }
+ if (keys.length === 0) return "";
+
+ return formatKeyText(compactRawKeys(keys))
+ .replace(/\bpageUp\b/g, "pgup")
+ .replace(/\bpageDown\b/g, "pgdn")
+ .replace(/\bup\b/g, "↑")
+ .replace(/\bdown\b/g, "↓")
+ .replace(/\bleft\b/g, "←")
+ .replace(/\bright\b/g, "→");
+}
+
+function compactRawKeys(keys: string[]): string {
+ if (keys.length === 1) return keys[0]!;
+
+ const parts = keys.map((key) => {
+ const separatorIndex = key.lastIndexOf("+");
+ return separatorIndex === -1
+ ? { prefix: "", suffix: key }
+ : { prefix: key.slice(0, separatorIndex + 1), suffix: key.slice(separatorIndex + 1) };
+ });
+ const prefix = parts[0]!.prefix;
+ return prefix && parts.every((part) => part.prefix === prefix)
+ ? `${prefix}${parts.map((part) => part.suffix).join("/")}`
+ : keys.join("/");
+}
+
/** Label input component shown when editing a label */
class LabelInput implements Component, Focusable {
private input: Input;
@@ -1181,25 +1334,7 @@ export class TreeSelectorComponent extends Container implements Focusable {
this.addChild(new Spacer(1));
this.addChild(new DynamicBorder());
this.addChild(new Text(theme.bold(" Session Tree"), 1, 0));
- const filterKeys = [
- keyText("app.tree.filter.default"),
- keyText("app.tree.filter.noTools"),
- keyText("app.tree.filter.userOnly"),
- keyText("app.tree.filter.labeledOnly"),
- keyText("app.tree.filter.all"),
- ].join("/");
- const cycleKeys = `${keyText("app.tree.filter.cycleForward")}/${keyText("app.tree.filter.cycleBackward")}`;
- const branchKeys = `${keyText("app.tree.foldOrUp")}/${keyText("app.tree.unfoldOrDown")}`;
- this.addChild(
- new TruncatedText(
- theme.fg(
- "muted",
- ` ↑/↓: move. ←/→: page. ${branchKeys}: fold/branch. ${keyText("app.tree.editLabel")}: label. ${filterKeys}: filters (${cycleKeys} cycle). ${keyText("app.tree.toggleLabelTimestamp")}: label time`,
- ),
- 0,
- 0,
- ),
- );
+ this.addChild(new TreeHelp());
this.addChild(new SearchLine(this.treeList));
this.addChild(new DynamicBorder());
this.addChild(new Spacer(1));
diff --git a/packages/coding-agent/src/modes/interactive/components/trust-selector.ts b/packages/coding-agent/src/modes/interactive/components/trust-selector.ts
index b7b1fe00..92c23288 100644
--- a/packages/coding-agent/src/modes/interactive/components/trust-selector.ts
+++ b/packages/coding-agent/src/modes/interactive/components/trust-selector.ts
@@ -1,7 +1,6 @@
import { Container, getKeybindings, Spacer, Text } from "@earendil-works/pi-tui";
import {
getProjectTrustOptions,
- getProjectTrustPath,
type ProjectTrustOption,
type ProjectTrustStoreEntry,
} from "../../../core/trust-manager.ts";
@@ -19,12 +18,12 @@ export interface TrustSelectorOptions {
onCancel: () => void;
}
-function formatDecision(cwd: string, decision: ProjectTrustStoreEntry | null): string {
+function formatDecision(trustPath: string | undefined, decision: ProjectTrustStoreEntry | null): string {
if (decision === null) {
return "none";
}
const label = decision.decision ? "trusted" : "untrusted";
- if (decision.path !== getProjectTrustPath(cwd)) {
+ if (trustPath !== undefined && decision.path !== trustPath) {
return `${label} (inherited from ${decision.path})`;
}
return `${label} (${decision.path})`;
@@ -56,7 +55,14 @@ export class TrustSelectorComponent extends Container {
this.addChild(new Text(theme.fg("muted", options.cwd), 1, 0));
this.addChild(new Spacer(1));
this.addChild(
- new Text(theme.fg("muted", `Saved decision: ${formatDecision(options.cwd, options.savedDecision)}`), 1, 0),
+ new Text(
+ theme.fg(
+ "muted",
+ `Saved decision: ${formatDecision(this.trustOptions[0]?.savedPath, options.savedDecision)}`,
+ ),
+ 1,
+ 0,
+ ),
);
this.addChild(
new Text(theme.fg("muted", `Current session: ${options.projectTrusted ? "trusted" : "untrusted"}`), 1, 0),
diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts
index 12e7e819..27e9f88a 100644
--- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts
+++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts
@@ -52,6 +52,7 @@ import { spawn, spawnSync } from "child_process";
import {
APP_NAME,
APP_TITLE,
+ CONFIG_DIR_NAME,
getAgentDir,
getAuthPath,
getDebugLogPath,
@@ -86,7 +87,7 @@ import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.ts";
import type { SourceInfo } from "../../core/source-info.ts";
import { isInstallTelemetryEnabled } from "../../core/telemetry.ts";
import type { TruncationResult } from "../../core/tools/truncate.ts";
-import { hasProjectConfigDir, hasProjectTrustInputs, ProjectTrustStore } from "../../core/trust-manager.ts";
+import { hasTrustRequiringProjectResources, ProjectTrustStore } from "../../core/trust-manager.ts";
import { getChangelogPath, getNewEntries, normalizeChangelogLinks, parseChangelog } from "../../utils/changelog.ts";
import { copyToClipboard } from "../../utils/clipboard.ts";
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.ts";
@@ -125,22 +126,21 @@ import { TreeSelectorComponent } from "./components/tree-selector.ts";
import { TrustSelectorComponent } from "./components/trust-selector.ts";
import { UserMessageComponent } from "./components/user-message.ts";
import { UserMessageSelectorComponent } from "./components/user-message-selector.ts";
+import { getModelSearchText } from "./model-search.ts";
import {
getAvailableThemes,
getAvailableThemesWithPaths,
getEditorTheme,
getMarkdownTheme,
getThemeByName,
- initTheme,
onThemeChange,
setRegisteredThemes,
- setTheme,
- setThemeInstance,
stopThemeWatcher,
Theme,
type ThemeColor,
theme,
} from "./theme/theme.ts";
+import { InteractiveThemeController } from "./theme/theme-controller.ts";
/** Interface for components that can be expanded/collapsed */
interface Expandable {
@@ -371,6 +371,7 @@ export class InteractiveMode {
private options: InteractiveModeOptions;
private autoTrustOnReloadCwd: string | undefined;
+ private themeController: InteractiveThemeController;
// Convenience accessors
private get session(): AgentSession {
@@ -394,7 +395,7 @@ export class InteractiveMode {
this.resetExtensionUI();
});
this.runtimeHost.setRebindSession(async () => {
- await this.rebindCurrentSession();
+ await this.rebindCurrentSession({ renderBeforeBind: true });
});
this.version = VERSION;
this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
@@ -425,7 +426,12 @@ export class InteractiveMode {
// Register themes from resource loader and initialize
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
- initTheme(this.settingsManager.getTheme(), true);
+ this.themeController = new InteractiveThemeController(
+ this.ui,
+ this.settingsManager,
+ (message) => this.showError(message),
+ () => this.updateEditorBorderColor(),
+ );
}
private getAutocompleteSourceTag(sourceInfo?: SourceInfo): string | undefined {
@@ -498,11 +504,12 @@ export class InteractiveMode {
const items = models.map((m) => ({
id: m.id,
provider: m.provider,
+ name: m.name,
label: `${m.provider}/${m.id}`,
}));
- // Fuzzy filter by model ID + provider (allows "opus anthropic" to match)
- const filtered = fuzzyFilter(items, prefix, (item) => `${item.id} ${item.provider}`);
+ // Fuzzy filter by model ID + provider in either order.
+ const filtered = fuzzyFilter(items, prefix, getModelSearchText);
if (filtered.length === 0) return null;
@@ -629,9 +636,28 @@ export class InteractiveMode {
console.log(theme.fg("dim", `Model scope: ${modelList}${cycleHint}`));
}
- // Add header container as first child
+ // Add header container as first child. Populate it after detectThemeIfUnset.
this.ui.addChild(this.headerContainer);
+ this.ui.addChild(this.chatContainer);
+ this.ui.addChild(this.pendingMessagesContainer);
+ this.ui.addChild(this.statusContainer);
+ this.renderWidgets(); // Initialize with default spacer
+ this.ui.addChild(this.widgetContainerAbove);
+ this.ui.addChild(this.editorContainer);
+ this.ui.addChild(this.widgetContainerBelow);
+ this.ui.addChild(this.footer);
+ this.ui.setFocus(this.editor);
+
+ this.setupKeyHandlers();
+ this.setupEditorSubmitHandler();
+
+ // Start the UI before initializing extensions so session_start handlers can use interactive dialogs
+ this.ui.start();
+ this.isInitialized = true;
+
+ await this.themeController.applyFromSettings();
+
// Add header with keybindings from config (unless silenced)
if (this.options.verbose || !this.settingsManager.getQuietStartup()) {
const logo = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`);
@@ -692,23 +718,7 @@ export class InteractiveMode {
this.builtInHeader = new Text("", 0, 0);
this.headerContainer.addChild(this.builtInHeader);
}
-
- this.ui.addChild(this.chatContainer);
- this.ui.addChild(this.pendingMessagesContainer);
- this.ui.addChild(this.statusContainer);
- this.renderWidgets(); // Initialize with default spacer
- this.ui.addChild(this.widgetContainerAbove);
- this.ui.addChild(this.editorContainer);
- this.ui.addChild(this.widgetContainerBelow);
- this.ui.addChild(this.footer);
- this.ui.setFocus(this.editor);
-
- this.setupKeyHandlers();
- this.setupEditorSubmitHandler();
-
- // Start the UI before initializing extensions so session_start handlers can use interactive dialogs
- this.ui.start();
- this.isInitialized = true;
+ this.ui.requestRender();
// Initialize extensions first so resources are shown before messages
await this.rebindCurrentSession();
@@ -1533,12 +1543,7 @@ export class InteractiveMode {
}
this.statusContainer.clear();
try {
- const result = await this.runtimeHost.newSession(options);
- if (!result.cancelled) {
- this.renderCurrentSessionState();
- this.ui.requestRender();
- }
- return result;
+ return await this.runtimeHost.newSession(options);
} catch (error: unknown) {
return this.handleFatalRuntimeError("Failed to create session", error);
}
@@ -1547,7 +1552,6 @@ export class InteractiveMode {
try {
const result = await this.runtimeHost.fork(entryId, options);
if (!result.cancelled) {
- this.renderCurrentSessionState();
this.editor.setText(result.selectedText ?? "");
this.showStatus("Forked to new session");
}
@@ -1621,12 +1625,18 @@ export class InteractiveMode {
}
}
- private async rebindCurrentSession(): Promise {
+ private async rebindCurrentSession(options: { renderBeforeBind?: boolean } = {}): Promise {
this.unsubscribe?.();
this.unsubscribe = undefined;
this.applyRuntimeSettings();
- await this.bindCurrentSessionExtensions();
- this.subscribeToAgent();
+ if (options.renderBeforeBind) {
+ this.renderCurrentSessionState();
+ this.subscribeToAgent();
+ await this.bindCurrentSessionExtensions();
+ } else {
+ await this.bindCurrentSessionExtensions();
+ this.subscribeToAgent();
+ }
await this.updateAvailableProviderCount();
this.updateEditorBorderColor();
this.updateTerminalTitle();
@@ -2054,16 +2064,13 @@ export class InteractiveMode {
getTheme: (name) => getThemeByName(name),
setTheme: (themeOrName) => {
if (themeOrName instanceof Theme) {
- setThemeInstance(themeOrName);
- this.ui.requestRender();
- return { success: true };
+ return this.themeController.setThemeInstance(themeOrName);
}
- const result = setTheme(themeOrName, true);
+ const result = this.themeController.setThemeName(themeOrName);
if (result.success) {
if (this.settingsManager.getTheme() !== themeOrName) {
this.settingsManager.setTheme(themeOrName);
}
- this.ui.requestRender();
}
return result;
},
@@ -3271,7 +3278,7 @@ export class InteractiveMode {
}
private renderProjectTrustWarningIfNeeded(): void {
- if (this.settingsManager.isProjectTrusted() || !hasProjectTrustInputs(this.sessionManager.getCwd())) {
+ if (this.settingsManager.isProjectTrusted() || !hasTrustRequiringProjectResources(this.sessionManager.getCwd())) {
return;
}
@@ -3282,7 +3289,7 @@ export class InteractiveMode {
new Text(
theme.fg(
"warning",
- "This project is not trusted. Project .pi resources and packages are ignored. Use /trust to save a trust decision, then restart pi.",
+ `This project is not trusted. Project ${CONFIG_DIR_NAME} resources and packages are ignored. Use /trust to save a trust decision, then restart pi.`,
),
1,
0,
@@ -3339,7 +3346,9 @@ export class InteractiveMode {
private async shutdown(options?: { fromSignal?: boolean }): Promise {
if (this.isShuttingDown) return;
this.isShuttingDown = true;
- this.unregisterSignalHandlers();
+ // Keep signal handlers registered until terminal cleanup has completed.
+ // `signal-exit` checks the listener list during the same SIGTERM/SIGHUP
+ // dispatch and re-sends the signal if only its own listeners remain.
if (options?.fromSignal) {
// Signal-triggered shutdown (SIGTERM/SIGHUP). Emit extension cleanup
@@ -3350,6 +3359,7 @@ export class InteractiveMode {
// which the stdout/stderr error handler turns into emergencyTerminalExit;
// the render loop is already idle, so this cannot hot-spin (see #4144).
await this.runtimeHost.dispose();
+ this.themeController.disableAutoSync();
await this.ui.terminal.drainInput(1000);
this.stop();
process.exit(0);
@@ -3360,6 +3370,7 @@ export class InteractiveMode {
// the final frame while the process is exiting.
// Drain any in-flight Kitty key release events before stopping.
// This prevents escape sequences from leaking to the parent shell over slow SSH.
+ this.themeController.disableAutoSync();
await this.ui.terminal.drainInput(1000);
this.stop();
@@ -3689,7 +3700,6 @@ export class InteractiveMode {
showError(errorMessage: string): void {
this.chatContainer.addChild(new Spacer(1));
this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`), 1, 0));
- this.chatContainer.addChild(new Spacer(1));
this.ui.requestRender();
}
@@ -3704,7 +3714,7 @@ export class InteractiveMode {
const updateInstruction = theme.fg("muted", `New version ${release.version} is available. Run `) + action;
const changelogUrl = "https://pi.dev/changelog";
const changelogLink = getCapabilities().hyperlinks
- ? hyperlink(theme.fg("accent", "open changelog"), changelogUrl)
+ ? hyperlink(theme.fg("accent", changelogUrl), changelogUrl)
: theme.fg("accent", changelogUrl);
const changelogLine = theme.fg("muted", "Changelog: ") + changelogLink;
const note = release.note?.trim();
@@ -3729,7 +3739,7 @@ export class InteractiveMode {
}
showPackageUpdateNotification(packages: string[]): void {
- const action = theme.fg("accent", `${APP_NAME} update`);
+ const action = theme.fg("accent", `${APP_NAME} update --extensions`);
const updateInstruction = theme.fg("muted", "Package updates are available. Run ") + action;
const packageLines = packages.map((pkg) => `- ${pkg}`).join("\n");
@@ -3963,7 +3973,8 @@ export class InteractiveMode {
httpIdleTimeoutMs: this.settingsManager.getHttpIdleTimeoutMs(),
thinkingLevel: this.session.thinkingLevel,
availableThinkingLevels: this.session.getAvailableThinkingLevels(),
- currentTheme: this.settingsManager.getTheme() || "dark",
+ currentTheme: this.settingsManager.getThemeSetting() || "dark",
+ terminalTheme: this.themeController.getTerminalTheme(),
availableThemes: getAvailableThemes(),
hideThinkingBlock: this.hideThinkingBlock,
collapseChangelog: this.settingsManager.getCollapseChangelog(),
@@ -4030,21 +4041,11 @@ export class InteractiveMode {
this.footer.invalidate();
this.updateEditorBorderColor();
},
- onThemeChange: (themeName) => {
- const result = setTheme(themeName, true);
- this.settingsManager.setTheme(themeName);
- this.ui.invalidate();
- if (!result.success) {
- this.showError(`Failed to load theme "${themeName}": ${result.error}\nFell back to dark theme.`);
- }
- },
- onThemePreview: (themeName) => {
- const result = setTheme(themeName, true);
- if (result.success) {
- this.ui.invalidate();
- this.ui.requestRender();
- }
+ onThemeChange: (themeSetting) => {
+ this.settingsManager.setTheme(themeSetting);
+ void this.themeController.applyFromSettings();
},
+ onThemePreview: (themeName) => this.themeController.preview(themeName),
onHideThinkingBlockChange: (hidden) => {
this.hideThinkingBlock = hidden;
this.settingsManager.setHideThinkingBlock(hidden);
@@ -4198,7 +4199,7 @@ export class InteractiveMode {
if (this.autoTrustOnReloadCwd !== cwd) {
return false;
}
- if (!this.settingsManager.isProjectTrusted() || !hasProjectConfigDir(cwd)) {
+ if (!this.settingsManager.isProjectTrusted() || !hasTrustRequiringProjectResources(cwd)) {
return false;
}
@@ -4375,7 +4376,6 @@ export class InteractiveMode {
return;
}
- this.renderCurrentSessionState();
this.editor.setText(result.selectedText ?? "");
done();
this.showStatus("Forked to new session");
@@ -4408,7 +4408,6 @@ export class InteractiveMode {
return;
}
- this.renderCurrentSessionState();
this.editor.setText("");
this.showStatus("Cloned to new session");
} catch (error: unknown) {
@@ -4600,7 +4599,6 @@ export class InteractiveMode {
if (result.cancelled) {
return result;
}
- this.renderCurrentSessionState();
this.showStatus("Resumed session");
return result;
} catch (error: unknown) {
@@ -4618,7 +4616,6 @@ export class InteractiveMode {
if (result.cancelled) {
return result;
}
- this.renderCurrentSessionState();
this.showStatus("Resumed session in current cwd");
return result;
}
@@ -5071,8 +5068,20 @@ export class InteractiveMode {
this.ui.requestRender();
};
+ let chatRestoredBeforeSessionStart = false;
+ let reloadBoxDismissed = false;
+ const restoreChatBeforeSessionStart = () => {
+ if (chatRestoredBeforeSessionStart) {
+ return;
+ }
+ this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
+ this.rebuildChatFromMessages();
+ chatRestoredBeforeSessionStart = true;
+ };
+
try {
- await this.session.reload();
+ await this.session.reload({ beforeSessionStart: restoreChatBeforeSessionStart });
+ restoreChatBeforeSessionStart();
configureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());
this.keybindings.reload();
const activeHeader = this.customHeader ?? this.builtInHeader;
@@ -5080,12 +5089,7 @@ export class InteractiveMode {
activeHeader.setExpanded(this.toolOutputExpanded);
}
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
- this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
- const themeName = this.settingsManager.getTheme();
- const themeResult = themeName ? setTheme(themeName, true) : { success: true };
- if (!themeResult.success) {
- this.showError(`Failed to load theme "${themeName}": ${themeResult.error}\nFell back to dark theme.`);
- }
+ await this.themeController.applyFromSettings();
const editorPaddingX = this.settingsManager.getEditorPaddingX();
const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
this.defaultEditor.setPaddingX(editorPaddingX);
@@ -5099,8 +5103,6 @@ export class InteractiveMode {
this.setupAutocompleteProvider();
const runner = this.session.extensionRunner;
this.setupExtensionShortcuts(runner);
- this.rebuildChatFromMessages();
- dismissReloadBox(this.editor as Component);
this.showLoadedResources({
force: false,
showDiagnosticsWhenQuiet: true,
@@ -5115,8 +5117,12 @@ export class InteractiveMode {
? "Reloaded keybindings, extensions, skills, prompts, themes; saved project trust"
: "Reloaded keybindings, extensions, skills, prompts, themes",
);
+ dismissReloadBox(this.editor as Component);
+ reloadBoxDismissed = true;
} catch (error) {
- dismissReloadBox(previousEditor as Component);
+ if (!reloadBoxDismissed) {
+ dismissReloadBox(previousEditor as Component);
+ }
this.showError(`Reload failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
@@ -5190,7 +5196,6 @@ export class InteractiveMode {
this.showStatus("Import cancelled");
return;
}
- this.renderCurrentSessionState();
this.showStatus(`Session imported from: ${inputPath}`);
} catch (error: unknown) {
if (error instanceof MissingSessionCwdError) {
@@ -5204,7 +5209,6 @@ export class InteractiveMode {
this.showStatus("Import cancelled");
return;
}
- this.renderCurrentSessionState();
this.showStatus(`Session imported from: ${inputPath}`);
return;
}
@@ -5543,7 +5547,6 @@ export class InteractiveMode {
if (result.cancelled) {
return;
}
- this.renderCurrentSessionState();
this.chatContainer.addChild(new Spacer(1));
this.chatContainer.addChild(new Text(`${theme.fg("accent", "✓ New session started")}`, 1, 1));
this.ui.requestRender();
@@ -5697,14 +5700,6 @@ export class InteractiveMode {
}
private async handleCompactCommand(customInstructions?: string): Promise {
- const entries = this.sessionManager.getEntries();
- const messageCount = entries.filter((e) => e.type === "message").length;
-
- if (messageCount < 2) {
- this.showWarning("Nothing to compact (no messages yet)");
- return;
- }
-
if (this.loadingAnimation) {
this.loadingAnimation.stop();
this.loadingAnimation = undefined;
@@ -5719,7 +5714,6 @@ export class InteractiveMode {
}
stop(): void {
- this.unregisterSignalHandlers();
if (this.settingsManager.getShowTerminalProgress()) {
this.ui.terminal.setProgress(false);
}
@@ -5727,6 +5721,7 @@ export class InteractiveMode {
this.loadingAnimation.stop();
this.loadingAnimation = undefined;
}
+ this.themeController.disableAutoSync();
this.clearExtensionTerminalInputListeners();
this.footer.dispose();
this.footerDataProvider.dispose();
@@ -5737,5 +5732,6 @@ export class InteractiveMode {
this.ui.stop();
this.isInitialized = false;
}
+ this.unregisterSignalHandlers();
}
}
diff --git a/packages/coding-agent/src/modes/interactive/model-search.ts b/packages/coding-agent/src/modes/interactive/model-search.ts
new file mode 100644
index 00000000..bab9c5a5
--- /dev/null
+++ b/packages/coding-agent/src/modes/interactive/model-search.ts
@@ -0,0 +1,21 @@
+export interface ModelSearchItem {
+ id: string;
+ provider: string;
+ name?: string;
+}
+
+export function getModelSearchText(item: ModelSearchItem): string {
+ const { id, provider } = item;
+ const name = item.name ? ` ${item.name}` : "";
+ return `${id} ${provider} ${provider}/${id} ${provider} ${id}${name}`;
+}
+
+/**
+ * The /model selector search should rank exact provider-prefixed queries before proxy-provider IDs
+ * like openrouter/openai/gpt-5, so keep the bare model ID out of the leading position.
+ */
+export function getModelSelectorSearchText(item: ModelSearchItem): string {
+ const { id, provider } = item;
+ const name = item.name ? ` ${item.name}` : "";
+ return `${provider} ${provider}/${id} ${provider} ${id}${name}`;
+}
diff --git a/packages/coding-agent/src/modes/interactive/theme/theme-controller.ts b/packages/coding-agent/src/modes/interactive/theme/theme-controller.ts
new file mode 100644
index 00000000..9fe9e8cc
--- /dev/null
+++ b/packages/coding-agent/src/modes/interactive/theme/theme-controller.ts
@@ -0,0 +1,135 @@
+import type { TUI } from "@earendil-works/pi-tui";
+import type { SettingsManager } from "../../../core/settings-manager.ts";
+import {
+ detectTerminalBackgroundFromEnv,
+ detectTerminalBackgroundTheme,
+ initTheme,
+ parseAutoThemeSetting,
+ resolveThemeSetting,
+ setTheme,
+ setThemeInstance,
+ type TerminalTheme,
+ type Theme,
+} from "./theme.ts";
+
+type ThemeResult = { success: boolean; error?: string };
+
+export class InteractiveThemeController {
+ private readonly ui: TUI;
+ private readonly settingsManager: SettingsManager;
+ private readonly showError: (message: string) => void;
+ private readonly onChanged: () => void;
+ private terminalTheme: TerminalTheme = detectTerminalBackgroundFromEnv().theme;
+ private activeThemeName: string | undefined;
+ private autoSyncEnabled = false;
+
+ constructor(ui: TUI, settingsManager: SettingsManager, showError: (message: string) => void, onChanged: () => void) {
+ this.ui = ui;
+ this.settingsManager = settingsManager;
+ this.showError = showError;
+ this.onChanged = onChanged;
+ this.activeThemeName = resolveThemeSetting(this.settingsManager.getThemeSetting(), this.terminalTheme);
+ initTheme(this.activeThemeName, true);
+ this.ui.onTerminalColorSchemeChange((terminalTheme) => this.applyTerminalTheme(terminalTheme));
+ }
+
+ async applyFromSettings(): Promise {
+ const themeSetting = this.settingsManager.getThemeSetting();
+ const autoTheme = parseAutoThemeSetting(themeSetting);
+ if (autoTheme) {
+ this.terminalTheme = await this.detectTerminalThemeForAuto();
+ this.setAutoSync(true);
+ this.applyThemeName(this.terminalTheme === "light" ? autoTheme.lightTheme : autoTheme.darkTheme, true);
+ return;
+ }
+
+ this.setAutoSync(false);
+ if (themeSetting !== undefined) {
+ this.applyThemeName(themeSetting, true);
+ return;
+ }
+
+ const detection = await detectTerminalBackgroundTheme({ ui: this.ui, timeoutMs: 100 });
+ this.terminalTheme = detection.theme;
+ if (!this.applyThemeName(detection.theme).success) return;
+ if (detection.confidence === "high") {
+ this.settingsManager.setTheme(detection.theme);
+ await this.settingsManager.flush();
+ }
+ }
+
+ setThemeName(themeName: string, showError = false): ThemeResult {
+ this.setAutoSync(false);
+ return this.applyThemeName(themeName, showError);
+ }
+
+ setThemeInstance(themeInstance: Theme): ThemeResult {
+ this.setAutoSync(false);
+ setThemeInstance(themeInstance);
+ this.activeThemeName = "";
+ this.notifyChanged();
+ return { success: true };
+ }
+
+ preview(themeSettingOrName: string): void {
+ const themeName = resolveThemeSetting(themeSettingOrName, this.terminalTheme) ?? this.activeThemeName;
+ if (!themeName) return;
+ if (setTheme(themeName, true).success) {
+ this.ui.invalidate();
+ this.ui.requestRender();
+ }
+ }
+
+ disableAutoSync(): void {
+ this.setAutoSync(false);
+ }
+
+ getTerminalTheme(): TerminalTheme {
+ return this.terminalTheme;
+ }
+
+ private applyThemeName(themeName: string, showError = false): ThemeResult {
+ const result = setTheme(themeName, true);
+ this.activeThemeName = result.success ? themeName : "dark";
+ this.notifyChanged();
+ if (!result.success && showError) {
+ this.showError(`Failed to load theme "${themeName}": ${result.error}\nFell back to dark theme.`);
+ }
+ return result;
+ }
+
+ private notifyChanged(): void {
+ this.ui.invalidate();
+ this.onChanged();
+ }
+
+ private setAutoSync(enabled: boolean): void {
+ if (this.autoSyncEnabled === enabled) return;
+ this.autoSyncEnabled = enabled;
+ this.ui.setTerminalColorSchemeNotifications(enabled);
+ }
+
+ private async detectTerminalThemeForAuto(): Promise {
+ try {
+ const colorScheme = await this.ui.queryTerminalColorScheme({ timeoutMs: 100 });
+ if (colorScheme) return colorScheme;
+ } catch {
+ // Fall back to OSC 11 / COLORFGBG detection when color-scheme DSR is unsupported.
+ }
+ return (await detectTerminalBackgroundTheme({ ui: this.ui, timeoutMs: 100 })).theme;
+ }
+
+ private applyTerminalTheme(terminalTheme: TerminalTheme): void {
+ if (!this.autoSyncEnabled) return;
+ this.terminalTheme = terminalTheme;
+ const autoTheme = parseAutoThemeSetting(this.settingsManager.getThemeSetting());
+ if (!autoTheme) {
+ this.setAutoSync(false);
+ return;
+ }
+ const themeName = terminalTheme === "light" ? autoTheme.lightTheme : autoTheme.darkTheme;
+ if (themeName !== this.activeThemeName) {
+ this.applyThemeName(themeName);
+ }
+ }
+}
diff --git a/packages/coding-agent/src/modes/interactive/theme/theme-schema.json b/packages/coding-agent/src/modes/interactive/theme/theme-schema.json
index 7bc495da..9d94a12a 100644
--- a/packages/coding-agent/src/modes/interactive/theme/theme-schema.json
+++ b/packages/coding-agent/src/modes/interactive/theme/theme-schema.json
@@ -11,7 +11,8 @@
},
"name": {
"type": "string",
- "description": "Theme name"
+ "pattern": "^[^/]+$",
+ "description": "Theme name. Must not contain '/' because it is reserved for automatic light/dark theme settings."
},
"vars": {
"type": "object",
diff --git a/packages/coding-agent/src/modes/interactive/theme/theme.ts b/packages/coding-agent/src/modes/interactive/theme/theme.ts
index 8bdf4816..58e5aac3 100644
--- a/packages/coding-agent/src/modes/interactive/theme/theme.ts
+++ b/packages/coding-agent/src/modes/interactive/theme/theme.ts
@@ -4,6 +4,7 @@ import {
type EditorTheme,
getCapabilities,
type MarkdownTheme,
+ type RgbColor,
type SelectListTheme,
type SettingsListTheme,
} from "@earendil-works/pi-tui";
@@ -502,6 +503,14 @@ function getCustomThemeInfos(): ThemeInfo[] {
return result;
}
+function assertThemeNameIsValid(name: string): void {
+ if (name.includes("/")) {
+ throw new Error(
+ `Invalid theme name "${name}": theme names cannot contain "/" because it is reserved for automatic light/dark theme settings.`,
+ );
+ }
+}
+
function parseThemeJson(label: string, json: unknown): ThemeJson {
if (!validateThemeJson.Check(json)) {
const errors = Array.from(validateThemeJson.Errors(json));
@@ -538,7 +547,9 @@ function parseThemeJson(label: string, json: unknown): ThemeJson {
throw new Error(errorMessage);
}
- return json as ThemeJson;
+ const themeJson = json as ThemeJson;
+ assertThemeNameIsValid(themeJson.name);
+ return themeJson;
}
function parseThemeJsonContent(label: string, content: string): ThemeJson {
@@ -624,10 +635,34 @@ export function getThemeByName(name: string): Theme | undefined {
export type TerminalTheme = "dark" | "light";
-export interface RgbColor {
- r: number;
- g: number;
- b: number;
+export function parseAutoThemeSetting(
+ themeSetting: string | undefined,
+): { lightTheme: string; darkTheme: string } | undefined {
+ if (!themeSetting) return undefined;
+ const slashIndex = themeSetting.indexOf("/");
+ if (slashIndex === -1 || themeSetting.indexOf("/", slashIndex + 1) !== -1) {
+ return undefined;
+ }
+
+ const lightTheme = themeSetting.slice(0, slashIndex).trim();
+ const darkTheme = themeSetting.slice(slashIndex + 1).trim();
+ if (!lightTheme || !darkTheme) {
+ return undefined;
+ }
+ return { lightTheme, darkTheme };
+}
+
+export function resolveThemeSetting(
+ themeSetting: string | undefined,
+ terminalTheme: TerminalTheme,
+): string | undefined {
+ const autoTheme = parseAutoThemeSetting(themeSetting);
+ if (autoTheme) {
+ return terminalTheme === "light" ? autoTheme.lightTheme : autoTheme.darkTheme;
+ }
+ if (themeSetting?.includes("/")) return undefined;
+ if (typeof themeSetting === "string") return themeSetting;
+ return undefined;
}
export interface TerminalThemeDetection {
@@ -641,6 +676,15 @@ export interface TerminalThemeDetectionOptions {
env?: NodeJS.ProcessEnv;
}
+export interface TerminalBackgroundThemeDetector {
+ queryTerminalBackgroundColor({ timeoutMs }: { timeoutMs: number }): Promise;
+}
+
+export interface TerminalBackgroundThemeDetectionOptions extends TerminalThemeDetectionOptions {
+ ui: TerminalBackgroundThemeDetector;
+ timeoutMs: number;
+}
+
function getColorFgBgBackgroundIndex(colorfgbg: string): number | undefined {
const parts = colorfgbg.split(";");
for (let i = parts.length - 1; i >= 0; i--) {
@@ -668,50 +712,7 @@ export function getThemeForRgbColor(rgb: RgbColor): TerminalTheme {
return getRgbColorLuminance(rgb) >= 0.5 ? "light" : "dark";
}
-function parseOscHexChannel(channel: string): number | undefined {
- if (!/^[0-9a-f]+$/i.test(channel)) {
- return undefined;
- }
- const max = 16 ** channel.length - 1;
- if (max <= 0) {
- return undefined;
- }
- return Math.round((parseInt(channel, 16) / max) * 255);
-}
-
-export function parseOsc11BackgroundColor(data: string): RgbColor | undefined {
- const match = data.match(/^\x1b\]11;([^\x07\x1b]*)(?:\x07|\x1b\\)$/i);
- if (!match) {
- return undefined;
- }
-
- const value = match[1].trim();
- if (value.startsWith("#")) {
- const hex = value.slice(1);
- if (/^[0-9a-f]{6}$/i.test(hex)) {
- return hexToRgb(value);
- }
- if (/^[0-9a-f]{12}$/i.test(hex)) {
- const r = parseOscHexChannel(hex.slice(0, 4));
- const g = parseOscHexChannel(hex.slice(4, 8));
- const b = parseOscHexChannel(hex.slice(8, 12));
- return r !== undefined && g !== undefined && b !== undefined ? { r, g, b } : undefined;
- }
- return undefined;
- }
-
- const rgbValue = value.replace(/^rgba?:/i, "");
- const [red, green, blue] = rgbValue.split("/");
- if (red === undefined || green === undefined || blue === undefined) {
- return undefined;
- }
- const r = parseOscHexChannel(red);
- const g = parseOscHexChannel(green);
- const b = parseOscHexChannel(blue);
- return r !== undefined && g !== undefined && b !== undefined ? { r, g, b } : undefined;
-}
-
-export function detectTerminalBackground(options: TerminalThemeDetectionOptions = {}): TerminalThemeDetection {
+export function detectTerminalBackgroundFromEnv(options: TerminalThemeDetectionOptions = {}): TerminalThemeDetection {
const env = options.env ?? process.env;
const colorfgbg = env.COLORFGBG || "";
const bg = getColorFgBgBackgroundIndex(colorfgbg);
@@ -732,8 +733,30 @@ export function detectTerminalBackground(options: TerminalThemeDetectionOptions
};
}
+export async function detectTerminalBackgroundTheme({
+ ui,
+ timeoutMs,
+ env,
+}: TerminalBackgroundThemeDetectionOptions): Promise {
+ try {
+ const rgb = await ui.queryTerminalBackgroundColor({ timeoutMs });
+ if (rgb) {
+ return {
+ theme: getThemeForRgbColor(rgb),
+ source: "terminal background",
+ detail: `OSC 11 background rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,
+ confidence: "high",
+ };
+ }
+ } catch {
+ // Fall back to environment-based detection when the terminal query fails.
+ }
+
+ return detectTerminalBackgroundFromEnv({ env });
+}
+
export function getDefaultTheme(): string {
- return detectTerminalBackground().theme;
+ return detectTerminalBackgroundFromEnv().theme;
}
// ============================================================================
@@ -769,6 +792,7 @@ export function setRegisteredThemes(themes: Theme[]): void {
registeredThemes.clear();
for (const theme of themes) {
if (theme.name) {
+ assertThemeNameIsValid(theme.name);
registeredThemes.set(theme.name, theme);
}
}
diff --git a/packages/coding-agent/src/modes/rpc/rpc-mode.ts b/packages/coding-agent/src/modes/rpc/rpc-mode.ts
index 9bb1f089..1150b826 100644
--- a/packages/coding-agent/src/modes/rpc/rpc-mode.ts
+++ b/packages/coding-agent/src/modes/rpc/rpc-mode.ts
@@ -667,7 +667,7 @@ export async function runRpcMode(runtimeHost: AgentSessionRuntime): Promise [-l] [--approve|--no-approve]`;
case "update":
- return `${APP_NAME} update [source|self|pi] [--self] [--extensions] [--extension ] [--approve|--no-approve] [--force]`;
+ return `${APP_NAME} update [source|self|pi] [--self|--extensions|--all] [--extension ] [--approve|--no-approve] [--force]`;
case "list":
return `${APP_NAME} list [--approve|--no-approve]`;
}
@@ -93,7 +96,7 @@ function printPackageCommandHelp(command: PackageCommand): void {
Install a package and add it to settings.
Options:
- -l, --local Install project-locally (.pi/settings.json)
+ -l, --local Install project-locally (${CONFIG_DIR_NAME}/settings.json)
-a, --approve Trust project-local files for this command
-na, --no-approve Ignore project-local files for this command
@@ -115,7 +118,7 @@ Remove a package and its source from settings.
Alias: ${APP_NAME} uninstall [-l]
Options:
- -l, --local Remove from project settings (.pi/settings.json)
+ -l, --local Remove from project settings (${CONFIG_DIR_NAME}/settings.json)
-a, --approve Trust project-local files for this command
-na, --no-approve Ignore project-local files for this command
@@ -132,15 +135,17 @@ Examples:
Update pi and installed packages.
Options:
- --self Update pi only
+ --self Update pi only (default when no target is given)
--extensions Update installed packages only
+ --all Update pi and installed packages
--extension Update one package only
-a, --approve Trust project-local files for this command
-na, --no-approve Ignore project-local files for this command
--force Reinstall pi even if the current version is latest
Short forms:
- ${APP_NAME} update Update pi and all extensions
+ ${APP_NAME} update Update pi only
+ ${APP_NAME} update --all Update pi and all extensions
${APP_NAME} update Update one package
${APP_NAME} update pi Update pi only (self works as alias to pi)
`);
@@ -183,6 +188,7 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
let source: string | undefined;
let selfFlag = false;
let extensionsFlag = false;
+ let allFlag = false;
let extensionFlagSource: string | undefined;
for (let index = 0; index < rest.length; index++) {
@@ -219,6 +225,15 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
continue;
}
+ if (arg === "--all") {
+ if (command === "update") {
+ allFlag = true;
+ } else {
+ invalidOption = invalidOption ?? arg;
+ }
+ continue;
+ }
+
if (arg === "--approve" || arg === "-a") {
projectTrustOverride = true;
continue;
@@ -270,10 +285,20 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
}
let updateTarget: UpdateTarget | undefined;
+ let showExtensionsSkippedNote = false;
if (command === "update") {
+ if (allFlag && (selfFlag || extensionsFlag || extensionFlagSource)) {
+ conflictingOptions =
+ conflictingOptions ?? "--all cannot be combined with --self, --extensions, or --extension";
+ }
+ if (allFlag && source) {
+ conflictingOptions = conflictingOptions ?? "--all cannot be combined with a positional source";
+ }
+
if (extensionFlagSource) {
- if (selfFlag || extensionsFlag) {
- conflictingOptions = conflictingOptions ?? "--extension cannot be combined with --self or --extensions";
+ if (selfFlag || extensionsFlag || allFlag) {
+ conflictingOptions =
+ conflictingOptions ?? "--extension cannot be combined with --self, --extensions, or --all";
}
if (source) {
conflictingOptions = conflictingOptions ?? "--extension cannot be combined with a positional source";
@@ -284,12 +309,15 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
if (sourceIsSelf) {
updateTarget = extensionsFlag ? { type: "all" } : { type: "self" };
} else {
- if (extensionsFlag || selfFlag) {
+ if (extensionsFlag || selfFlag || allFlag) {
conflictingOptions =
- conflictingOptions ?? "positional update targets cannot be combined with --self or --extensions";
+ conflictingOptions ??
+ "positional update targets cannot be combined with --self, --extensions, or --all";
}
updateTarget = { type: "extensions", source };
}
+ } else if (allFlag) {
+ updateTarget = { type: "all" };
} else if (selfFlag && extensionsFlag) {
updateTarget = { type: "all" };
} else if (selfFlag) {
@@ -297,7 +325,8 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
} else if (extensionsFlag) {
updateTarget = { type: "extensions" };
} else {
- updateTarget = { type: "all" };
+ updateTarget = { type: "self" };
+ showExtensionsSkippedNote = true;
}
}
@@ -305,6 +334,7 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
command,
source,
updateTarget,
+ showExtensionsSkippedNote,
local,
force,
projectTrustOverride,
@@ -324,9 +354,12 @@ function updateTargetIncludesExtensions(target: UpdateTarget): boolean {
return target.type === "all" || target.type === "extensions";
}
-function printSelfUpdateUnavailable(npmCommand?: string[], updatePackageName = PACKAGE_NAME): void {
+function printSelfUpdateUnavailable(
+ npmCommand?: string[],
+ updatePackageTarget: SelfUpdatePackageTarget = PACKAGE_NAME,
+): void {
console.error(`error: ${APP_NAME} cannot self-update this installation.`);
- console.error(getSelfUpdateUnavailableInstruction(PACKAGE_NAME, npmCommand, updatePackageName));
+ console.error(getSelfUpdateUnavailableInstruction(PACKAGE_NAME, npmCommand, updatePackageTarget));
const entrypoint = process.argv[1];
if (entrypoint) {
@@ -361,27 +394,38 @@ function printSelfUpdateNote(note: string): void {
interface SelfUpdatePlan {
packageName: string;
+ installSpec: string;
+ version: string;
shouldRun: boolean;
note?: string;
}
async function getSelfUpdatePlan(force: boolean): Promise