Merge branch 'main' into async-file-tools

This commit is contained in:
Armin Ronacher
2026-05-20 17:14:01 +02:00
Unverified
295 changed files with 3915 additions and 1034 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
sudo ln -s $(which fdfind) /usr/local/bin/fd
- name: Install dependencies
run: npm ci
run: npm ci --ignore-scripts
- name: Build
run: npm run build
+31
View File
@@ -0,0 +1,31 @@
name: npm audit
on:
schedule:
- cron: '37 7 * * *'
workflow_dispatch:
permissions:
contents: read
jobs:
audit:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies without lifecycle scripts
run: npm ci --ignore-scripts --no-audit --no-fund
- name: Audit production vulnerabilities
run: npm audit --omit=dev --audit-level=moderate
- name: Verify registry signatures
run: npm audit signatures --omit=dev
+5
View File
@@ -3,6 +3,11 @@
# Get list of staged files before running check
STAGED_FILES=$(git diff --cached --name-only)
node scripts/check-lockfile-commit.mjs
if [ $? -ne 0 ]; then
exit 1
fi
# Run the check script (formatting, linting, and type checking)
echo "Running formatting, linting, and type checking..."
npm run check
+2
View File
@@ -0,0 +1,2 @@
save-exact=true
min-release-age=2
+14 -2
View File
@@ -190,13 +190,25 @@ Create provider file exporting:
1. **Update CHANGELOGs**: Ensure all changes since last release are documented in the `[Unreleased]` section of each affected package's CHANGELOG.md
2. **Run release script**:
2. **Soft gate: local release smoke test**: Before running the real release script, build an unpublished local release and manually smoke test it from outside the repository so it cannot accidentally resolve workspace files:
```bash
npm run release:local -- --out /tmp/pi-local-release --force
cd /tmp
/tmp/pi-local-release/node/pi --help
/tmp/pi-local-release/node/pi --version
/tmp/pi-local-release/node/pi
/tmp/pi-local-release/bun/pi --help
/tmp/pi-local-release/bun/pi --version
```
In the interactive smoke test, verify startup, model/account listing, and at least one real prompt with the intended default provider. Treat failures as release blockers unless the user explicitly accepts the risk.
3. **Run release script**:
```bash
npm run release:patch # Fixes and additions
npm run release:minor # API breaking changes
```
The script handles: version bump, CHANGELOG finalization, commit, tag, publish, and adding new `[Unreleased]` sections.
The release script handles: version bump, CHANGELOG finalization, commit, tag, publish, and adding new `[Unreleased]` sections.
## **CRITICAL** Git Rules for Parallel Agents **CRITICAL**
+14
View File
@@ -70,6 +70,20 @@ npm run check # Lint, format, and type check
./pi-test.sh # Run pi from sources (can be run from any directory)
```
## Supply-chain hardening
We treat npm dependency changes as reviewed code changes.
- Direct external dependencies are pinned to exact versions. Internal workspace packages remain version-ranged.
- `.npmrc` sets `save-exact=true` and `min-release-age=2` to avoid same-day dependency releases during npm resolution.
- `package-lock.json` is the dependency ground truth. Pre-commit blocks accidental lockfile commits unless `PI_ALLOW_LOCKFILE_CHANGE=1` is set.
- `npm run check` verifies pinned direct deps, native TypeScript import compatibility, and the generated coding-agent shrinkwrap.
- The published CLI package includes `packages/coding-agent/npm-shrinkwrap.json`, generated from the root lockfile, to pin transitive deps for npm users.
- Release smoke tests use `npm run release:local` to build, pack, and create isolated npm and Bun installs outside the repo before publishing.
- Local release installs, documented npm installs, and `pi update --self` use `--ignore-scripts` where supported.
- 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.
## License
MIT
+60 -62
View File
@@ -14,19 +14,17 @@
"packages/coding-agent/examples/extensions/custom-provider-gitlab-duo",
"packages/coding-agent/examples/extensions/sandbox"
],
"dependencies": {
"@earendil-works/pi-coding-agent": "^0.30.2"
},
"devDependencies": {
"@anthropic-ai/sandbox-runtime": "^0.0.26",
"@anthropic-ai/sandbox-runtime": "0.0.26",
"@biomejs/biome": "2.3.5",
"@types/node": "^22.10.5",
"@types/node": "22.19.19",
"@typescript/native-preview": "7.0.0-dev.20260120.1",
"husky": "^9.1.7",
"jiti": "^2.7.0",
"shx": "^0.4.0",
"tsx": "^4.20.3",
"typescript": "^5.9.2"
"esbuild": "0.28.0",
"husky": "9.1.7",
"jiti": "2.7.0",
"shx": "0.4.0",
"tsx": "4.22.1",
"typescript": "5.9.3"
},
"engines": {
"node": ">=22.19.0"
@@ -6004,15 +6002,15 @@
"license": "MIT",
"dependencies": {
"@earendil-works/pi-ai": "^0.75.3",
"ignore": "^7.0.5",
"typebox": "^1.1.24",
"yaml": "^2.8.2"
"ignore": "7.0.5",
"typebox": "1.1.38",
"yaml": "2.9.0"
},
"devDependencies": {
"@types/node": "^24.3.0",
"@vitest/coverage-v8": "^3.2.4",
"typescript": "^5.7.3",
"vitest": "^3.2.4"
"@types/node": "24.12.4",
"@vitest/coverage-v8": "3.2.4",
"typescript": "5.9.3",
"vitest": "3.2.4"
},
"engines": {
"node": ">=22.19.0"
@@ -6040,23 +6038,23 @@
"version": "0.75.3",
"license": "MIT",
"dependencies": {
"@anthropic-ai/sdk": "^0.91.1",
"@aws-sdk/client-bedrock-runtime": "^3.1030.0",
"@google/genai": "^1.40.0",
"@mistralai/mistralai": "^2.2.0",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6",
"@anthropic-ai/sdk": "0.91.1",
"@aws-sdk/client-bedrock-runtime": "3.1048.0",
"@google/genai": "1.52.0",
"@mistralai/mistralai": "2.2.1",
"http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.6",
"openai": "6.26.0",
"partial-json": "^0.1.7",
"typebox": "^1.1.24"
"partial-json": "0.1.7",
"typebox": "1.1.38"
},
"bin": {
"pi-ai": "dist/cli.js"
},
"devDependencies": {
"@types/node": "^24.3.0",
"canvas": "^3.2.0",
"vitest": "^3.2.4"
"@types/node": "24.12.4",
"canvas": "3.2.3",
"vitest": "3.2.4"
},
"engines": {
"node": ">=22.19.0"
@@ -6087,47 +6085,47 @@
"@earendil-works/pi-agent-core": "^0.75.3",
"@earendil-works/pi-ai": "^0.75.3",
"@earendil-works/pi-tui": "^0.75.3",
"@silvia-odwyer/photon-node": "^0.3.4",
"chalk": "^5.5.0",
"cross-spawn": "^7.0.6",
"diff": "^8.0.2",
"glob": "^13.0.1",
"highlight.js": "^10.7.3",
"hosted-git-info": "^9.0.2",
"ignore": "^7.0.5",
"jiti": "^2.7.0",
"minimatch": "^10.2.3",
"proper-lockfile": "^4.1.2",
"typebox": "^1.1.24",
"undici": "^8.3.0",
"yaml": "^2.8.2"
"@silvia-odwyer/photon-node": "0.3.4",
"chalk": "5.6.2",
"cross-spawn": "7.0.6",
"diff": "8.0.4",
"glob": "13.0.6",
"highlight.js": "10.7.3",
"hosted-git-info": "9.0.3",
"ignore": "7.0.5",
"jiti": "2.7.0",
"minimatch": "10.2.5",
"proper-lockfile": "4.1.2",
"typebox": "1.1.38",
"undici": "8.3.0",
"yaml": "2.9.0"
},
"bin": {
"pi": "dist/cli.js"
},
"devDependencies": {
"@types/cross-spawn": "^6.0.6",
"@types/diff": "^7.0.2",
"@types/hosted-git-info": "^3.0.5",
"@types/ms": "^2.1.0",
"@types/node": "^24.3.0",
"@types/proper-lockfile": "^4.1.4",
"shx": "^0.4.0",
"typescript": "^5.7.3",
"vitest": "^3.2.4"
"@types/cross-spawn": "6.0.6",
"@types/diff": "7.0.2",
"@types/hosted-git-info": "3.0.5",
"@types/ms": "2.1.0",
"@types/node": "24.12.4",
"@types/proper-lockfile": "4.1.4",
"shx": "0.4.0",
"typescript": "5.9.3",
"vitest": "3.2.4"
},
"engines": {
"node": ">=22.19.0"
},
"optionalDependencies": {
"@mariozechner/clipboard": "^0.3.6"
"@mariozechner/clipboard": "0.3.6"
}
},
"packages/coding-agent/examples/extensions/custom-provider-anthropic": {
"name": "pi-extension-custom-provider-anthropic",
"version": "0.75.3",
"dependencies": {
"@anthropic-ai/sdk": "^0.52.0"
"@anthropic-ai/sdk": "0.52.0"
}
},
"packages/coding-agent/examples/extensions/custom-provider-gitlab-duo": {
@@ -6138,17 +6136,17 @@
"name": "pi-extension-sandbox",
"version": "1.5.3",
"dependencies": {
"@anthropic-ai/sandbox-runtime": "^0.0.26"
"@anthropic-ai/sandbox-runtime": "0.0.26"
}
},
"packages/coding-agent/examples/extensions/with-deps": {
"name": "pi-extension-with-deps",
"version": "0.75.3",
"dependencies": {
"ms": "^2.1.3"
"ms": "2.1.3"
},
"devDependencies": {
"@types/ms": "^2.1.0"
"@types/ms": "2.1.0"
}
},
"packages/coding-agent/node_modules/@anthropic-ai/sdk": {
@@ -6182,19 +6180,19 @@
"version": "0.75.3",
"license": "MIT",
"dependencies": {
"get-east-asian-width": "^1.3.0",
"marked": "^15.0.12"
"get-east-asian-width": "1.6.0",
"marked": "15.0.12"
},
"devDependencies": {
"@xterm/headless": "^5.5.0",
"@xterm/xterm": "^5.5.0",
"chalk": "^5.5.0"
"@xterm/headless": "5.5.0",
"@xterm/xterm": "5.5.0",
"chalk": "5.6.2"
},
"engines": {
"node": ">=22.19.0"
},
"optionalDependencies": {
"koffi": "^2.9.0"
"koffi": "2.16.2"
}
}
}
+17 -14
View File
@@ -12,41 +12,44 @@
"scripts": {
"clean": "npm run clean --workspaces",
"build": "cd packages/tui && npm run build && cd ../ai && npm run build && cd ../agent && npm run build && cd ../coding-agent && npm run build",
"check": "biome check --write --error-on-warnings . && tsgo --noEmit && npm run check:browser-smoke",
"check": "biome check --write --error-on-warnings . && npm run check:pinned-deps && npm run check:ts-imports && npm run check:shrinkwrap && tsgo --noEmit && npm run check:browser-smoke",
"check:browser-smoke": "node scripts/check-browser-smoke.mjs",
"check:pinned-deps": "node scripts/check-pinned-deps.mjs",
"check:shrinkwrap": "node scripts/generate-coding-agent-shrinkwrap.mjs --check",
"check:ts-imports": "node scripts/check-ts-relative-imports.mjs",
"profile:tui": "node scripts/profile-coding-agent-node.mjs --mode tui",
"profile:rpc": "node scripts/profile-coding-agent-node.mjs --mode rpc",
"test": "npm run test --workspaces --if-present",
"version:patch": "npm version patch -ws --no-git-tag-version && node scripts/sync-versions.js && shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install",
"version:minor": "npm version minor -ws --no-git-tag-version && node scripts/sync-versions.js && shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install",
"version:major": "npm version major -ws --no-git-tag-version && node scripts/sync-versions.js && shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install",
"version:patch": "npm version patch -ws --no-git-tag-version && node scripts/sync-versions.js && npm install --package-lock-only",
"version:minor": "npm version minor -ws --no-git-tag-version && node scripts/sync-versions.js && npm install --package-lock-only",
"version:major": "npm version major -ws --no-git-tag-version && node scripts/sync-versions.js && npm install --package-lock-only",
"version:set": "npm version -ws",
"prepublishOnly": "npm run clean && npm run build && npm run check",
"publish": "npm run prepublishOnly && npm publish -ws --access public",
"publish:dry": "npm run prepublishOnly && npm publish -ws --access public --dry-run",
"release:local": "node scripts/local-release.mjs",
"shrinkwrap:coding-agent": "node scripts/generate-coding-agent-shrinkwrap.mjs",
"release:patch": "node scripts/release.mjs patch",
"release:minor": "node scripts/release.mjs minor",
"release:major": "node scripts/release.mjs major",
"prepare": "husky"
},
"devDependencies": {
"@anthropic-ai/sandbox-runtime": "^0.0.26",
"@anthropic-ai/sandbox-runtime": "0.0.26",
"@biomejs/biome": "2.3.5",
"@types/node": "^22.10.5",
"@types/node": "22.19.19",
"@typescript/native-preview": "7.0.0-dev.20260120.1",
"husky": "^9.1.7",
"jiti": "^2.7.0",
"tsx": "^4.20.3",
"typescript": "^5.9.2",
"shx": "^0.4.0"
"esbuild": "0.28.0",
"husky": "9.1.7",
"jiti": "2.7.0",
"shx": "0.4.0",
"tsx": "4.22.1",
"typescript": "5.9.3"
},
"engines": {
"node": ">=22.19.0"
},
"version": "0.0.3",
"dependencies": {
"@earendil-works/pi-coding-agent": "^0.30.2"
},
"overrides": {
"rimraf": "6.1.2",
"gaxios": {
+7 -7
View File
@@ -30,9 +30,9 @@
},
"dependencies": {
"@earendil-works/pi-ai": "^0.75.3",
"ignore": "^7.0.5",
"typebox": "^1.1.24",
"yaml": "^2.8.2"
"ignore": "7.0.5",
"typebox": "1.1.38",
"yaml": "2.9.0"
},
"keywords": [
"ai",
@@ -52,9 +52,9 @@
"node": ">=22.19.0"
},
"devDependencies": {
"@types/node": "^24.3.0",
"@vitest/coverage-v8": "^3.2.4",
"typescript": "^5.7.3",
"vitest": "^3.2.4"
"@types/node": "24.12.4",
"@vitest/coverage-v8": "3.2.4",
"typescript": "5.9.3",
"vitest": "3.2.4"
}
}
+2 -2
View File
@@ -8,8 +8,8 @@ import {
} from "@earendil-works/pi-ai";
import { Type } from "typebox";
import { describe, expect, it } from "vitest";
import { agentLoop, agentLoopContinue } from "../src/agent-loop.js";
import type { AgentContext, AgentEvent, AgentLoopConfig, AgentMessage, AgentTool } from "../src/types.js";
import { agentLoop, agentLoopContinue } from "../src/agent-loop.ts";
import type { AgentContext, AgentEvent, AgentLoopConfig, AgentMessage, AgentTool } from "../src/types.ts";
// Mock stream for testing - mimics MockAssistantStream
class MockAssistantStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
+1 -1
View File
@@ -1,6 +1,6 @@
import { type AssistantMessage, type AssistantMessageEvent, EventStream, getModel } from "@earendil-works/pi-ai";
import { describe, expect, it } from "vitest";
import { Agent } from "../src/index.js";
import { Agent } from "../src/index.ts";
// Mock stream that mimics AssistantMessageEventStream
class MockAssistantStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
+2 -2
View File
@@ -11,8 +11,8 @@ import {
type UserMessage,
} from "@earendil-works/pi-ai";
import { afterEach, describe, expect, it } from "vitest";
import { Agent, type AgentEvent } from "../src/index.js";
import { calculateTool } from "./utils/calculate.js";
import { Agent, type AgentEvent } from "../src/index.ts";
import { calculateTool } from "./utils/calculate.ts";
const registrations: FauxProviderRegistration[] = [];
@@ -1,10 +1,10 @@
import { fauxAssistantMessage, fauxToolCall, registerFauxProvider, type StreamOptions } from "@earendil-works/pi-ai";
import { afterEach, describe, expect, it } from "vitest";
import { AgentHarness } from "../../src/harness/agent-harness.js";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.js";
import { InMemorySessionStorage } from "../../src/harness/session/memory-storage.js";
import { Session } from "../../src/harness/session/session.js";
import { calculateTool } from "../utils/calculate.js";
import { AgentHarness } from "../../src/harness/agent-harness.ts";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.ts";
import { InMemorySessionStorage } from "../../src/harness/session/memory-storage.ts";
import { Session } from "../../src/harness/session/session.ts";
import { calculateTool } from "../utils/calculate.ts";
const registrations: Array<{ unregister(): void }> = [];
@@ -1,13 +1,13 @@
import { fauxAssistantMessage, fauxToolCall, getModel, registerFauxProvider } from "@earendil-works/pi-ai";
import { afterEach, describe, expect, it } from "vitest";
import { AgentHarness } from "../../src/harness/agent-harness.js";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.js";
import { InMemorySessionStorage } from "../../src/harness/session/memory-storage.js";
import { Session } from "../../src/harness/session/session.js";
import type { PromptTemplate, Skill } from "../../src/harness/types.js";
import type { AgentMessage, AgentTool } from "../../src/types.js";
import { calculateTool } from "../utils/calculate.js";
import { getCurrentTimeTool } from "../utils/get-current-time.js";
import { AgentHarness } from "../../src/harness/agent-harness.ts";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.ts";
import { InMemorySessionStorage } from "../../src/harness/session/memory-storage.ts";
import { Session } from "../../src/harness/session/session.ts";
import type { PromptTemplate, Skill } from "../../src/harness/types.ts";
import type { AgentMessage, AgentTool } from "../../src/types.ts";
import { calculateTool } from "../utils/calculate.ts";
import { getCurrentTimeTool } from "../utils/get-current-time.ts";
interface AppSkill extends Skill {
source: "project" | "user";
@@ -22,8 +22,8 @@ import {
prepareCompaction,
serializeConversation,
shouldCompact,
} from "../../src/harness/compaction/compaction.js";
import { buildSessionContext } from "../../src/harness/session/session.js";
} from "../../src/harness/compaction/compaction.ts";
import { buildSessionContext } from "../../src/harness/session/session.ts";
import type {
BranchSummaryEntry,
CompactionEntry,
@@ -33,9 +33,9 @@ import type {
ModelChangeEntry,
SessionTreeEntry,
ThinkingLevelChangeEntry,
} from "../../src/harness/types.js";
import { getOrThrow } from "../../src/harness/types.js";
import type { AgentMessage } from "../../src/types.js";
} from "../../src/harness/types.ts";
import { getOrThrow } from "../../src/harness/types.ts";
import type { AgentMessage } from "../../src/types.ts";
let nextId = 0;
function createId(): string {
@@ -1,10 +1,10 @@
import { access, chmod, realpath, symlink } from "node:fs/promises";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.js";
import { FileError, getOrThrow } from "../../src/harness/types.js";
import { executeShellWithCapture } from "../../src/harness/utils/shell-output.js";
import { createTempDir } from "./session-test-utils.js";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.ts";
import { FileError, getOrThrow } from "../../src/harness/types.ts";
import { executeShellWithCapture } from "../../src/harness/utils/shell-output.ts";
import { createTempDir } from "./session-test-utils.ts";
const chmodRestorePaths: string[] = [];
@@ -1,13 +1,13 @@
import { symlink } from "node:fs/promises";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.js";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.ts";
import {
formatPromptTemplateInvocation,
loadPromptTemplates,
loadSourcedPromptTemplates,
} from "../../src/harness/prompt-templates.js";
import { createTempDir } from "./session-test-utils.js";
} from "../../src/harness/prompt-templates.ts";
import { createTempDir } from "./session-test-utils.ts";
describe("loadPromptTemplates", () => {
it("loads markdown templates non-recursively from one or more dirs", async () => {
+4 -4
View File
@@ -1,9 +1,9 @@
import { existsSync } from "node:fs";
import { describe, expect, it } from "vitest";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.js";
import { JsonlSessionRepo } from "../../src/harness/session/jsonl-repo.js";
import { InMemorySessionRepo } from "../../src/harness/session/memory-repo.js";
import { createAssistantMessage, createTempDir, createUserMessage } from "./session-test-utils.js";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.ts";
import { JsonlSessionRepo } from "../../src/harness/session/jsonl-repo.ts";
import { InMemorySessionRepo } from "../../src/harness/session/memory-repo.ts";
import { createAssistantMessage, createTempDir, createUserMessage } from "./session-test-utils.ts";
describe("InMemorySessionRepo", () => {
it("opens, deletes, and forks by metadata", async () => {
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { formatPromptTemplateInvocation } from "../../src/harness/prompt-templates.js";
import { formatSkillInvocation } from "../../src/harness/skills.js";
import { formatPromptTemplateInvocation } from "../../src/harness/prompt-templates.ts";
import { formatSkillInvocation } from "../../src/harness/skills.ts";
describe("resource formatting helpers", () => {
it("formats skill invocations with additional instructions", () => {
@@ -1,5 +1,5 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { uuidv7 } from "../../src/harness/session/uuid.js";
import { uuidv7 } from "../../src/harness/session/uuid.ts";
const UUID_V7_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
const TIMESTAMP = 0x0123456789ab;
+6 -6
View File
@@ -1,12 +1,12 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.js";
import { JsonlSessionStorage } from "../../src/harness/session/jsonl-storage.js";
import { InMemorySessionStorage } from "../../src/harness/session/memory-storage.js";
import { Session } from "../../src/harness/session/session.js";
import type { SessionStorage } from "../../src/harness/types.js";
import { createAssistantMessage, createTempDir, createUserMessage, getLatestTempDir } from "./session-test-utils.js";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.ts";
import { JsonlSessionStorage } from "../../src/harness/session/jsonl-storage.ts";
import { InMemorySessionStorage } from "../../src/harness/session/memory-storage.ts";
import { Session } from "../../src/harness/session/session.ts";
import type { SessionStorage } from "../../src/harness/types.ts";
import { createAssistantMessage, createTempDir, createUserMessage, getLatestTempDir } from "./session-test-utils.ts";
async function runSessionSuite(
name: string,
+3 -3
View File
@@ -1,9 +1,9 @@
import { symlink } from "node:fs/promises";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.js";
import { loadSkills, loadSourcedSkills } from "../../src/harness/skills.js";
import { createTempDir } from "./session-test-utils.js";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.ts";
import { loadSkills, loadSourcedSkills } from "../../src/harness/skills.ts";
import { createTempDir } from "./session-test-utils.ts";
describe("loadSkills", () => {
it("loads SKILL.md files through the execution environment", async () => {
+5 -5
View File
@@ -1,11 +1,11 @@
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.js";
import { JsonlSessionStorage, loadJsonlSessionMetadata } from "../../src/harness/session/jsonl-storage.js";
import { InMemorySessionStorage } from "../../src/harness/session/memory-storage.js";
import { type MessageEntry, ok, type SessionMetadata } from "../../src/harness/types.js";
import { createAssistantMessage, createTempDir, createUserMessage } from "./session-test-utils.js";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.ts";
import { JsonlSessionStorage, loadJsonlSessionMetadata } from "../../src/harness/session/jsonl-storage.ts";
import { InMemorySessionStorage } from "../../src/harness/session/memory-storage.ts";
import { type MessageEntry, ok, type SessionMetadata } from "../../src/harness/types.ts";
import { createAssistantMessage, createTempDir, createUserMessage } from "./session-test-utils.ts";
describe("InMemorySessionStorage", () => {
it("returns configured session metadata", async () => {
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { formatSkillsForSystemPrompt } from "../../src/harness/system-prompt.js";
import { formatSkillsForSystemPrompt } from "../../src/harness/system-prompt.ts";
const visibleSkill = {
name: "visible",
+1 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { truncateHead, truncateTail } from "../../src/harness/utils/truncate.js";
import { truncateHead, truncateTail } from "../../src/harness/utils/truncate.ts";
const encoder = new TextEncoder();
+4 -3
View File
@@ -1,8 +1,8 @@
import { homedir } from "node:os";
import { join } from "node:path";
import { getModel } from "@earendil-works/pi-ai";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.js";
import { InMemorySessionStorage } from "../../src/harness/session/memory-storage.js";
import { NodeExecutionEnv } from "../../src/harness/env/nodejs.ts";
import { InMemorySessionStorage } from "../../src/harness/session/memory-storage.ts";
import {
AgentHarness,
formatSkillsForSystemPrompt,
@@ -11,13 +11,14 @@ import {
type PromptTemplate,
Session,
type Skill,
} from "../../src/index.js";
} from "../../src/index.ts";
type Source = { type: "project" | "user" | "path"; dir: string };
type SourcedSkill = Skill & { source: Source };
type SourcedPromptTemplate = PromptTemplate & { source: Source };
const env = new NodeExecutionEnv({ cwd: process.cwd() });
const source = (type: Source["type"], dir: string) => ({ path: dir, source: { type, dir } });
const { skills: sourcedSkills } = await loadSourcedSkills<Source, SourcedSkill>(
env,
+1 -1
View File
@@ -1,5 +1,5 @@
import { type Static, Type } from "typebox";
import type { AgentTool, AgentToolResult } from "../../src/types.js";
import type { AgentTool, AgentToolResult } from "../../src/types.ts";
export interface CalculateResult extends AgentToolResult<undefined> {
content: Array<{ type: "text"; text: string }>;
@@ -1,5 +1,5 @@
import { type Static, Type } from "typebox";
import type { AgentTool, AgentToolResult } from "../../src/types.js";
import type { AgentTool, AgentToolResult } from "../../src/types.ts";
export interface GetCurrentTimeResult extends AgentToolResult<{ utcTimestamp: number }> {}
+13 -13
View File
@@ -60,22 +60,22 @@
],
"scripts": {
"clean": "shx rm -rf dist",
"generate-models": "npx tsx scripts/generate-models.ts",
"generate-image-models": "npx tsx scripts/generate-image-models.ts",
"generate-models": "node scripts/generate-models.ts",
"generate-image-models": "node scripts/generate-image-models.ts",
"build": "npm run generate-models && npm run generate-image-models && tsgo -p tsconfig.build.json",
"test": "vitest --run",
"prepublishOnly": "npm run clean && npm run build"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.91.1",
"@aws-sdk/client-bedrock-runtime": "^3.1030.0",
"@google/genai": "^1.40.0",
"@mistralai/mistralai": "^2.2.0",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6",
"@anthropic-ai/sdk": "0.91.1",
"@aws-sdk/client-bedrock-runtime": "3.1048.0",
"@google/genai": "1.52.0",
"@mistralai/mistralai": "2.2.1",
"http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.6",
"openai": "6.26.0",
"partial-json": "^0.1.7",
"typebox": "^1.1.24"
"partial-json": "0.1.7",
"typebox": "1.1.38"
},
"keywords": [
"ai",
@@ -98,8 +98,8 @@
"node": ">=22.19.0"
},
"devDependencies": {
"@types/node": "^24.3.0",
"canvas": "^3.2.0",
"vitest": "^3.2.4"
"@types/node": "24.12.4",
"canvas": "3.2.3",
"vitest": "3.2.4"
}
}
+2 -2
View File
@@ -1,9 +1,9 @@
#!/usr/bin/env tsx
#!/usr/bin/env node
import { writeFileSync } from "fs";
import { dirname, join } from "path";
import { fileURLToPath } from "url";
import type { ImagesModel } from "../src/types.js";
import type { ImagesModel } from "../src/types.ts";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
+3 -9
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env tsx
#!/usr/bin/env node
import { writeFileSync } from "fs";
import { join, dirname } from "path";
@@ -8,14 +8,8 @@ import {
CLOUDFLARE_AI_GATEWAY_COMPAT_BASE_URL,
CLOUDFLARE_AI_GATEWAY_OPENAI_BASE_URL,
CLOUDFLARE_WORKERS_AI_BASE_URL,
} from "../src/providers/cloudflare.js";
import {
Api,
type AnthropicMessagesCompat,
KnownProvider,
Model,
type OpenAICompletionsCompat,
} from "../src/types.js";
} from "../src/providers/cloudflare.ts";
import type { AnthropicMessagesCompat, Api, KnownProvider, Model, OpenAICompletionsCompat } from "../src/types.ts";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
+2 -3
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env tsx
#!/usr/bin/env node
import { createCanvas } from "canvas";
import { writeFileSync } from "fs";
import { mkdirSync, writeFileSync } from "fs";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
@@ -27,7 +27,6 @@ const buffer = canvas.toBuffer("image/png");
const outputPath = join(__dirname, "..", "test", "data", "red-circle.png");
// Ensure the directory exists
import { mkdirSync } from "fs";
mkdirSync(join(__dirname, "..", "test", "data"), { recursive: true });
writeFileSync(outputPath, buffer);
+6 -6
View File
@@ -1,13 +1,13 @@
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { complete, stream } from "../src/stream.js";
import type { Api, Context, Model, StreamOptions } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { complete, stream } from "../src/stream.ts";
import type { Api, Context, Model, StreamOptions } from "../src/types.ts";
type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.js";
import { hasBedrockCredentials } from "./bedrock-utils.js";
import { resolveApiKey } from "./oauth.js";
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.ts";
import { hasBedrockCredentials } from "./bedrock-utils.ts";
import { resolveApiKey } from "./oauth.ts";
// Resolve OAuth tokens at module level (async, runs before tests)
const [openaiCodexToken] = await Promise.all([resolveApiKey("openai-codex")]);
@@ -2,8 +2,8 @@ import { createServer, type IncomingMessage, type ServerResponse } from "node:ht
import type { AddressInfo } from "node:net";
import { Type } from "typebox";
import { describe, expect, it } from "vitest";
import { streamAnthropic } from "../src/providers/anthropic.js";
import type { Context, Model, Tool } from "../src/types.js";
import { streamAnthropic } from "../src/providers/anthropic.ts";
import type { Context, Model, Tool } from "../src/types.ts";
interface CapturedRequest {
headers: IncomingMessage["headers"];
@@ -1,10 +1,10 @@
import { Type } from "typebox";
import { describe, expect, it } from "vitest";
import { getEnvApiKey } from "../src/env-api-keys.js";
import { getModels, getProviders } from "../src/models.js";
import { complete } from "../src/stream.js";
import type { Api, KnownProvider, Model, ProviderStreamOptions, Tool } from "../src/types.js";
import { resolveApiKey } from "./oauth.js";
import { getEnvApiKey } from "../src/env-api-keys.ts";
import { getModels, getProviders } from "../src/models.ts";
import { complete } from "../src/stream.ts";
import type { Api, KnownProvider, Model, ProviderStreamOptions, Tool } from "../src/types.ts";
import { resolveApiKey } from "./oauth.ts";
const githubCopilotToken = await resolveApiKey("github-copilot");
@@ -1,9 +1,9 @@
import { describe, expect, it } from "vitest";
import { getEnvApiKey } from "../src/env-api-keys.js";
import { getModels, getProviders } from "../src/models.js";
import { complete } from "../src/stream.js";
import type { Api, KnownProvider, Model, ProviderStreamOptions } from "../src/types.js";
import { resolveApiKey } from "./oauth.js";
import { getEnvApiKey } from "../src/env-api-keys.ts";
import { getModels, getProviders } from "../src/models.ts";
import { complete } from "../src/stream.ts";
import type { Api, KnownProvider, Model, ProviderStreamOptions } from "../src/types.ts";
import { resolveApiKey } from "./oauth.ts";
const githubCopilotToken = await resolveApiKey("github-copilot");
+1 -1
View File
@@ -1,5 +1,5 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { loginAnthropic, refreshAnthropicToken } from "../src/utils/oauth/anthropic.js";
import { loginAnthropic, refreshAnthropicToken } from "../src/utils/oauth/anthropic.ts";
function jsonResponse(body: unknown, status: number = 200): Response {
return new Response(JSON.stringify(body), {
@@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { streamSimple } from "../src/stream.js";
import type { Context } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { streamSimple } from "../src/stream.ts";
import type { Context } from "../src/types.ts";
interface AnthropicThinkingPayload {
thinking?: { type: string };
@@ -1,9 +1,9 @@
import type Anthropic from "@anthropic-ai/sdk";
import { Type } from "typebox";
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { streamAnthropic } from "../src/providers/anthropic.js";
import type { Context, ToolCall } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { streamAnthropic } from "../src/providers/anthropic.ts";
import type { Context, ToolCall } 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");
@@ -1,13 +1,20 @@
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { streamSimple } from "../src/stream.js";
import type { Context, Model, SimpleStreamOptions } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { streamSimple } from "../src/stream.ts";
import type { Context, Model, SimpleStreamOptions } from "../src/types.ts";
interface AnthropicThinkingPayload {
thinking?: { type: string; budget_tokens?: number; display?: string };
output_config?: { effort?: string };
}
class PayloadCaptured extends Error {
constructor() {
super("payload captured");
this.name = "PayloadCaptured";
}
}
function makePayloadCaptureContext(): Context {
return {
messages: [{ role: "user", content: "Hello", timestamp: Date.now() }],
@@ -29,7 +36,7 @@ async function capturePayload(
apiKey: "fake-key",
onPayload: (payload) => {
capturedPayload = payload as AnthropicThinkingPayload;
return payload;
throw new PayloadCaptured();
},
});
@@ -1,9 +1,9 @@
import { Type } from "typebox";
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { stream } from "../src/stream.js";
import type { Context, Tool } from "../src/types.js";
import { resolveApiKey } from "./oauth.js";
import { getModel } from "../src/models.ts";
import { stream } from "../src/stream.ts";
import type { Context, Tool } from "../src/types.ts";
import { resolveApiKey } from "./oauth.ts";
const oauthToken = await resolveApiKey("anthropic");
@@ -1,7 +1,7 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { getModel } from "../src/models.js";
import { streamAzureOpenAIResponses } from "../src/providers/azure-openai-responses.js";
import type { Context } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { streamAzureOpenAIResponses } from "../src/providers/azure-openai-responses.ts";
import type { Context } from "../src/types.ts";
interface CapturedAzureClientOptions {
apiKey: string;
@@ -44,9 +44,9 @@ vi.mock("@aws-sdk/client-bedrock-runtime", () => {
};
});
import { getModel } from "../src/models.js";
import { streamBedrock } from "../src/providers/amazon-bedrock.js";
import type { Context, Message } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { streamBedrock } from "../src/providers/amazon-bedrock.ts";
import type { Context, Message } from "../src/types.ts";
const baseModel = getModel("amazon-bedrock", "us.anthropic.claude-sonnet-4-5-20250929-v1:0");
@@ -44,9 +44,9 @@ vi.mock("@aws-sdk/client-bedrock-runtime", () => {
};
});
import { getModel } from "../src/models.js";
import { streamBedrock } from "../src/providers/amazon-bedrock.js";
import type { Context, Model } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { streamBedrock } from "../src/providers/amazon-bedrock.ts";
import type { Context, Model } from "../src/types.ts";
const context: Context = {
messages: [{ role: "user", content: "hello", timestamp: Date.now() }],
+4 -4
View File
@@ -17,10 +17,10 @@
*/
import { describe, expect, it } from "vitest";
import { getModels } from "../src/models.js";
import { complete } from "../src/stream.js";
import type { Context } from "../src/types.js";
import { hasBedrockCredentials } from "./bedrock-utils.js";
import { getModels } from "../src/models.ts";
import { complete } from "../src/stream.ts";
import type { Context } from "../src/types.ts";
import { hasBedrockCredentials } from "./bedrock-utils.ts";
describe("Amazon Bedrock Models", () => {
const models = getModels("amazon-bedrock");
@@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { type BedrockOptions, streamBedrock } from "../src/providers/amazon-bedrock.js";
import type { Context, Model } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { type BedrockOptions, streamBedrock } from "../src/providers/amazon-bedrock.ts";
import type { Context, Model } from "../src/types.ts";
interface BedrockThinkingPayload {
additionalModelRequestFields?: {
@@ -11,6 +11,13 @@ interface BedrockThinkingPayload {
};
}
class PayloadCaptured extends Error {
constructor() {
super("payload captured");
this.name = "PayloadCaptured";
}
}
function makeContext(): Context {
return {
messages: [{ role: "user", content: "Hello", timestamp: Date.now() }],
@@ -25,10 +32,9 @@ async function capturePayload(
const s = streamBedrock(model, makeContext(), {
...options,
reasoning: options?.reasoning ?? "high",
signal: AbortSignal.abort(),
onPayload: (payload) => {
capturedPayload = payload as BedrockThinkingPayload;
return payload;
throw new PayloadCaptured();
},
});
@@ -137,10 +143,9 @@ describe("Application inference profile support", () => {
messages: [{ role: "user", content: "Hello", timestamp: Date.now() }],
},
{
signal: AbortSignal.abort(),
onPayload: (payload) => {
capturedPayload = payload;
return payload;
throw new PayloadCaptured();
},
},
);
+50 -52
View File
@@ -1,7 +1,24 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { stream } from "../src/stream.js";
import type { Context, Model } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { streamAnthropic } from "../src/providers/anthropic.ts";
import { streamOpenAICompletions } from "../src/providers/openai-completions.ts";
import { streamOpenAIResponses } from "../src/providers/openai-responses.ts";
import { stream } from "../src/stream.ts";
import type { Context, Model } from "../src/types.ts";
class PayloadCaptured extends Error {
constructor() {
super("payload captured");
this.name = "PayloadCaptured";
}
}
function stopAfterPayload<TPayload>(capture: (payload: TPayload) => void): (payload: unknown) => never {
return (payload: unknown): never => {
capture(payload as TPayload);
throw new PayloadCaptured();
};
}
describe("Cache Retention (PI_CACHE_RETENTION)", () => {
const originalEnv = process.env.PI_CACHE_RETENTION;
@@ -31,9 +48,9 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
let capturedPayload: any = null;
const s = stream(model, context, {
onPayload: (payload) => {
onPayload: stopAfterPayload((payload) => {
capturedPayload = payload;
},
}),
});
// Consume the stream to trigger the request
@@ -54,9 +71,9 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
let capturedPayload: any = null;
const s = stream(model, context, {
onPayload: (payload) => {
onPayload: stopAfterPayload((payload) => {
capturedPayload = payload;
},
}),
});
// Consume the stream to trigger the request
@@ -88,14 +105,13 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
// Since we can't easily test this without mocking, we'll skip the actual API call
// and just verify the helper logic works correctly
const { streamAnthropic } = await import("../src/providers/anthropic.js");
try {
const s = streamAnthropic(proxyModel, context, {
apiKey: "fake-key",
onPayload: (payload) => {
onPayload: stopAfterPayload((payload) => {
capturedPayload = payload;
},
}),
});
// This will fail since we're using a fake key and fake proxy, but the payload should be captured
@@ -119,15 +135,13 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
};
let capturedPayload: any = null;
const { streamAnthropic } = await import("../src/providers/anthropic.js");
try {
const s = streamAnthropic(proxyModel, context, {
apiKey: "fake-key",
cacheRetention: "long",
onPayload: (payload) => {
onPayload: stopAfterPayload((payload) => {
capturedPayload = payload;
},
}),
});
for await (const event of s) {
@@ -145,15 +159,13 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
const baseModel = getModel("anthropic", "claude-haiku-4-5");
let capturedPayload: any = null;
const { streamAnthropic } = await import("../src/providers/anthropic.js");
try {
const s = streamAnthropic(baseModel, context, {
apiKey: "fake-key",
cacheRetention: "none",
onPayload: (payload) => {
onPayload: stopAfterPayload((payload) => {
capturedPayload = payload;
},
}),
});
for await (const event of s) {
@@ -171,14 +183,12 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
const baseModel = getModel("anthropic", "claude-haiku-4-5");
let capturedPayload: any = null;
const { streamAnthropic } = await import("../src/providers/anthropic.js");
try {
const s = streamAnthropic(baseModel, context, {
apiKey: "fake-key",
onPayload: (payload) => {
onPayload: stopAfterPayload((payload) => {
capturedPayload = payload;
},
}),
});
for await (const event of s) {
@@ -199,15 +209,13 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
const baseModel = getModel("anthropic", "claude-haiku-4-5");
let capturedPayload: any = null;
const { streamAnthropic } = await import("../src/providers/anthropic.js");
try {
const s = streamAnthropic(baseModel, context, {
apiKey: "fake-key",
cacheRetention: "long",
onPayload: (payload) => {
onPayload: stopAfterPayload((payload) => {
capturedPayload = payload;
},
}),
});
for await (const event of s) {
@@ -230,9 +238,9 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
let capturedPayload: any = null;
const s = stream(model, context, {
onPayload: (payload) => {
onPayload: stopAfterPayload((payload) => {
capturedPayload = payload;
},
}),
});
// Consume the stream to trigger the request
@@ -253,9 +261,9 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
let capturedPayload: any = null;
const s = stream(model, context, {
onPayload: (payload) => {
onPayload: stopAfterPayload((payload) => {
capturedPayload = payload;
},
}),
});
// Consume the stream to trigger the request
@@ -280,14 +288,12 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
let capturedPayload: any = null;
const { streamOpenAIResponses } = await import("../src/providers/openai-responses.js");
try {
const s = streamOpenAIResponses(proxyModel, context, {
apiKey: "fake-key",
onPayload: (payload) => {
onPayload: stopAfterPayload((payload) => {
capturedPayload = payload;
},
}),
});
// This will fail since we're using a fake key and fake proxy, but the payload should be captured
@@ -309,16 +315,14 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
};
let capturedPayload: any = null;
const { streamOpenAIResponses } = await import("../src/providers/openai-responses.js");
try {
const s = streamOpenAIResponses(model, context, {
apiKey: "fake-key",
cacheRetention: "long",
sessionId: "session-compat-false",
onPayload: (payload) => {
onPayload: stopAfterPayload((payload) => {
capturedPayload = payload;
},
}),
});
for await (const event of s) {
@@ -336,16 +340,14 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
const model = getModel("openai", "gpt-4o-mini");
let capturedPayload: any = null;
const { streamOpenAIResponses } = await import("../src/providers/openai-responses.js");
try {
const s = streamOpenAIResponses(model, context, {
apiKey: "fake-key",
cacheRetention: "none",
sessionId: "session-1",
onPayload: (payload) => {
onPayload: stopAfterPayload((payload) => {
capturedPayload = payload;
},
}),
});
for await (const event of s) {
@@ -364,16 +366,14 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
const model = getModel("openai", "gpt-4o-mini");
let capturedPayload: any = null;
const { streamOpenAIResponses } = await import("../src/providers/openai-responses.js");
try {
const s = streamOpenAIResponses(model, context, {
apiKey: "fake-key",
cacheRetention: "long",
sessionId: "session-2",
onPayload: (payload) => {
onPayload: stopAfterPayload((payload) => {
capturedPayload = payload;
},
}),
});
for await (const event of s) {
@@ -408,16 +408,15 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
it("should set prompt_cache_retention for non-api.openai.com baseUrl by default", async () => {
let capturedPayload: any = null;
const { streamOpenAICompletions } = await import("../src/providers/openai-completions.js");
try {
const s = streamOpenAICompletions(createCompletionsModel(), context, {
apiKey: "fake-key",
cacheRetention: "long",
sessionId: "session-completions",
onPayload: (payload) => {
onPayload: stopAfterPayload((payload) => {
capturedPayload = payload;
},
}),
});
for await (const event of s) {
@@ -434,16 +433,15 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => {
it("should omit prompt_cache_retention when supportsLongCacheRetention is false", async () => {
let capturedPayload: any = null;
const { streamOpenAICompletions } = await import("../src/providers/openai-completions.js");
try {
const s = streamOpenAICompletions(createCompletionsModel({ supportsLongCacheRetention: false }), context, {
apiKey: "fake-key",
cacheRetention: "long",
sessionId: "session-completions-false",
onPayload: (payload) => {
onPayload: stopAfterPayload((payload) => {
capturedPayload = payload;
},
}),
});
for await (const event of s) {
@@ -1,4 +1,4 @@
#!/usr/bin/env tsx
#!/usr/bin/env node
/**
* Live probe for OpenAI Codex Responses websocket-cached mode.
*
@@ -9,15 +9,15 @@
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { Type } from "typebox";
import { AuthStorage } from "../../coding-agent/src/core/auth-storage.js";
import { getModel } from "../src/models.js";
import { AuthStorage } from "../../coding-agent/src/core/auth-storage.ts";
import { getModel } from "../src/models.ts";
import {
closeOpenAICodexWebSocketSessions,
getOpenAICodexWebSocketDebugStats,
resetOpenAICodexWebSocketDebugStats,
streamOpenAICodexResponses,
} from "../src/providers/openai-codex-responses.js";
import type { AssistantMessage, Context, Message, Model, Tool, ToolResultMessage, Transport } from "../src/types.js";
} from "../src/providers/openai-codex-responses.ts";
import type { AssistantMessage, Context, Message, Model, Tool, ToolResultMessage, Transport } from "../src/types.ts";
type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh";
@@ -85,7 +85,7 @@ function required(value: string | undefined, flag: string): string {
}
function printHelp(): void {
console.log(`Usage: npx tsx test/codex-websocket-cached-probe.ts [options]
console.log(`Usage: node test/codex-websocket-cached-probe.ts [options]
Options:
--turns <n> Number of user turns. Default: ${DEFAULT_TURNS}
+7 -7
View File
@@ -14,13 +14,13 @@
import type { ChildProcess } from "child_process";
import { execSync, spawn } from "child_process";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { complete } from "../src/stream.js";
import type { AssistantMessage, Context, Model, Usage } from "../src/types.js";
import { isContextOverflow } from "../src/utils/overflow.js";
import { hasAzureOpenAICredentials } from "./azure-utils.js";
import { hasBedrockCredentials } from "./bedrock-utils.js";
import { resolveApiKey } from "./oauth.js";
import { getModel } from "../src/models.ts";
import { complete } from "../src/stream.ts";
import type { AssistantMessage, Context, Model, Usage } from "../src/types.ts";
import { isContextOverflow } from "../src/utils/overflow.ts";
import { hasAzureOpenAICredentials } from "./azure-utils.ts";
import { hasBedrockCredentials } from "./bedrock-utils.ts";
import { resolveApiKey } from "./oauth.ts";
// Resolve OAuth tokens at module level (async, runs before tests)
const oauthTokens = await Promise.all([resolveApiKey("github-copilot"), resolveApiKey("openai-codex")]);
@@ -25,12 +25,12 @@
import { writeFileSync } from "fs";
import { Type } from "typebox";
import { beforeAll, describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { completeSimple, getEnvApiKey } from "../src/stream.js";
import type { Api, AssistantMessage, Message, Model, Tool, ToolResultMessage } from "../src/types.js";
import { hasAzureOpenAICredentials } from "./azure-utils.js";
import { hasCloudflareAiGatewayCredentials, hasCloudflareWorkersAICredentials } from "./cloudflare-utils.js";
import { resolveApiKey } from "./oauth.js";
import { getModel } from "../src/models.ts";
import { completeSimple, getEnvApiKey } from "../src/stream.ts";
import type { Api, AssistantMessage, Message, Model, Tool, ToolResultMessage } from "../src/types.ts";
import { hasAzureOpenAICredentials } from "./azure-utils.ts";
import { hasCloudflareAiGatewayCredentials, hasCloudflareWorkersAICredentials } from "./cloudflare-utils.ts";
import { resolveApiKey } from "./oauth.ts";
// Simple tool for testing
const testToolSchema = Type.Object({
+7 -7
View File
@@ -1,14 +1,14 @@
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { complete } from "../src/stream.js";
import type { Api, AssistantMessage, Context, Model, StreamOptions, UserMessage } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { complete } from "../src/stream.ts";
import type { Api, AssistantMessage, Context, Model, StreamOptions, UserMessage } from "../src/types.ts";
type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.js";
import { hasBedrockCredentials } from "./bedrock-utils.js";
import { hasCloudflareAiGatewayCredentials, hasCloudflareWorkersAICredentials } from "./cloudflare-utils.js";
import { resolveApiKey } from "./oauth.js";
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.ts";
import { hasBedrockCredentials } from "./bedrock-utils.ts";
import { hasCloudflareAiGatewayCredentials, hasCloudflareWorkersAICredentials } from "./cloudflare-utils.ts";
import { resolveApiKey } from "./oauth.ts";
// Resolve OAuth tokens at module level (async, runs before tests)
const oauthTokens = await Promise.all([
+1 -1
View File
@@ -1,5 +1,5 @@
import { afterEach, describe, expect, it } from "vitest";
import { findEnvKeys, getEnvApiKey } from "../src/env-api-keys.js";
import { findEnvKeys, getEnvApiKey } from "../src/env-api-keys.ts";
const originalCopilotGitHubToken = process.env.COPILOT_GITHUB_TOKEN;
const originalGhToken = process.env.GH_TOKEN;
+2 -2
View File
@@ -8,8 +8,8 @@ import {
registerFauxProvider,
stream,
Type,
} from "../src/index.js";
import type { AssistantMessageEvent, Context } from "../src/types.js";
} from "../src/index.ts";
import type { AssistantMessageEvent, Context } from "../src/types.ts";
async function collectEvents(streamResult: ReturnType<typeof stream>): Promise<AssistantMessageEvent[]> {
const events: AssistantMessageEvent[] = [];
+4 -4
View File
@@ -2,10 +2,10 @@ import { createServer, type IncomingMessage, type ServerResponse } from "node:ht
import type { AddressInfo } from "node:net";
import { Type } from "typebox";
import { afterEach, describe, expect, it } from "vitest";
import { findEnvKeys, getEnvApiKey } from "../src/env-api-keys.js";
import { getModel } from "../src/models.js";
import { streamAnthropic } from "../src/providers/anthropic.js";
import type { Context, Model, Tool } from "../src/types.js";
import { findEnvKeys, getEnvApiKey } from "../src/env-api-keys.ts";
import { getModel } from "../src/models.ts";
import { streamAnthropic } from "../src/providers/anthropic.ts";
import type { Context, Model, Tool } from "../src/types.ts";
const originalFireworksApiKey = process.env.FIREWORKS_API_KEY;
@@ -1,7 +1,7 @@
import { describe, expect, it, vi } from "vitest";
import { getModel } from "../src/models.js";
import { streamAnthropic } from "../src/providers/anthropic.js";
import type { Context } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { streamAnthropic } from "../src/providers/anthropic.ts";
import type { Context } from "../src/types.ts";
const mockState = vi.hoisted(() => ({
constructorOpts: undefined as Record<string, unknown> | undefined,
@@ -1,5 +1,5 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { loginGitHubCopilot } from "../src/utils/oauth/github-copilot.js";
import { loginGitHubCopilot } from "../src/utils/oauth/github-copilot.ts";
function jsonResponse(body: unknown, status: number = 200): Response {
return new Response(JSON.stringify(body), {
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { convertTools } from "../src/providers/google-shared.js";
import type { Tool } from "../src/types.js";
import { convertTools } from "../src/providers/google-shared.ts";
import type { Tool } from "../src/types.ts";
function makeTool(parameters: Record<string, unknown>): Tool {
return {
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { convertMessages } from "../src/providers/google-shared.js";
import type { Context, Model } from "../src/types.js";
import { convertMessages } from "../src/providers/google-shared.ts";
import type { Context, Model } from "../src/types.ts";
function makeGemini3Model<TApi extends "google-generative-ai" | "google-vertex">(
api: TApi,
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { convertMessages } from "../src/providers/google-shared.js";
import type { Context, Model } from "../src/types.js";
import { convertMessages } from "../src/providers/google-shared.ts";
import type { Context, Model } from "../src/types.ts";
function makeModel<TApi extends "google-generative-ai">(
api: TApi,
@@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { streamSimple } from "../src/stream.js";
import type { Api, Context, Model, SimpleStreamOptions } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { streamSimple } from "../src/stream.ts";
import type { Api, Context, Model, SimpleStreamOptions } from "../src/types.ts";
type SimpleOptionsWithExtras = SimpleStreamOptions & Record<string, unknown>;
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { isThinkingPart, retainThoughtSignature } from "../src/providers/google-shared.js";
import { isThinkingPart, retainThoughtSignature } from "../src/providers/google-shared.ts";
describe("Google thinking detection (thoughtSignature)", () => {
it("treats part.thought === true as thinking", () => {
@@ -45,9 +45,9 @@ vi.mock("@google/genai", () => {
};
});
import { getModel } from "../src/models.js";
import { streamGoogleVertex } from "../src/providers/google-vertex.js";
import type { Context, Model } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { streamGoogleVertex } from "../src/providers/google-vertex.ts";
import type { Context, Model } from "../src/types.ts";
const model = getModel("google-vertex", "gemini-3-flash-preview");
const context: Context = {
+6 -6
View File
@@ -2,15 +2,15 @@ import { readFileSync } from "node:fs";
import { join } from "node:path";
import { Type } from "typebox";
import { describe, expect, it } from "vitest";
import type { Api, Context, Model, Tool, ToolResultMessage } from "../src/index.js";
import { complete, getModel } from "../src/index.js";
import type { StreamOptions } from "../src/types.js";
import type { Api, Context, Model, Tool, ToolResultMessage } from "../src/index.ts";
import { complete, getModel } from "../src/index.ts";
import type { StreamOptions } from "../src/types.ts";
type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.js";
import { hasBedrockCredentials } from "./bedrock-utils.js";
import { resolveApiKey } from "./oauth.js";
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.ts";
import { hasBedrockCredentials } from "./bedrock-utils.ts";
import { resolveApiKey } from "./oauth.ts";
// Resolve OAuth tokens at module level (async, runs before tests)
const oauthTokens = await Promise.all([
+3 -3
View File
@@ -2,9 +2,9 @@ import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import { getImageModel } from "../src/image-models.js";
import { generateImages } from "../src/images.js";
import type { ImageContent, ImagesContext, ImagesModel, ProviderImagesOptions } from "../src/types.js";
import { getImageModel } from "../src/image-models.ts";
import { generateImages } from "../src/images.ts";
import type { ImageContent, ImagesContext, ImagesModel, ProviderImagesOptions } from "../src/types.ts";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -1,11 +1,11 @@
import { Type } from "typebox";
import { describe, expect, it } from "vitest";
import { getEnvApiKey } from "../src/env-api-keys.js";
import { getModel } from "../src/models.js";
import { completeSimple } from "../src/stream.js";
import type { Api, Context, Model, StopReason, Tool, ToolCall, ToolResultMessage } from "../src/types.js";
import { StringEnum } from "../src/utils/typebox-helpers.js";
import { hasBedrockCredentials } from "./bedrock-utils.js";
import { getEnvApiKey } from "../src/env-api-keys.ts";
import { getModel } from "../src/models.ts";
import { completeSimple } from "../src/stream.ts";
import type { Api, Context, Model, StopReason, Tool, ToolCall, ToolResultMessage } from "../src/types.ts";
import { StringEnum } from "../src/utils/typebox-helpers.ts";
import { hasBedrockCredentials } from "./bedrock-utils.ts";
const calculatorSchema = Type.Object({
a: Type.Number({ description: "First number" }),
+1 -4
View File
@@ -1,11 +1,8 @@
import { spawnSync } from "node:child_process";
import { createRequire } from "node:module";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
const require = createRequire(import.meta.url);
const tsxLoader = require.resolve("tsx/esm");
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const aiEntryUrl = new URL("../src/index.ts", import.meta.url).href;
@@ -42,7 +39,7 @@ function runProbe(action: string): ProbeResult {
console.log(JSON.stringify({ loadedSpecifiers: [...new Set(loaded)] }));
`;
const result = spawnSync(process.execPath, ["--import", tsxLoader, "--input-type=module", "--eval", script], {
const result = spawnSync(process.execPath, ["--input-type=module", "--eval", script], {
cwd: packageRoot,
encoding: "utf8",
});
@@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { streamSimple } from "../src/stream.js";
import type { Context, Model, SimpleStreamOptions } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { streamSimple } from "../src/stream.ts";
import type { Context, Model, SimpleStreamOptions } from "../src/types.ts";
interface MistralPayload {
promptMode?: "reasoning";
+3 -3
View File
@@ -1,8 +1,8 @@
import { Type } from "typebox";
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { complete } from "../src/stream.js";
import type { Context, Model } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { complete } from "../src/stream.ts";
import type { Context, Model } from "../src/types.ts";
interface MistralToolPayload {
tools?: Array<{
+1 -1
View File
@@ -1,5 +1,5 @@
import { afterEach, describe, expect, it } from "vitest";
import { resolveHttpProxyUrlForTarget, UNSUPPORTED_PROXY_PROTOCOL_MESSAGE } from "../src/utils/node-http-proxy.js";
import { resolveHttpProxyUrlForTarget, UNSUPPORTED_PROXY_PROTOCOL_MESSAGE } from "../src/utils/node-http-proxy.ts";
const PROXY_ENV_KEYS = [
"HTTP_PROXY",
+2 -2
View File
@@ -8,8 +8,8 @@
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { homedir } from "os";
import { dirname, join } from "path";
import { getOAuthApiKey } from "../src/utils/oauth/index.js";
import type { OAuthCredentials, OAuthProvider } from "../src/utils/oauth/types.js";
import { getOAuthApiKey } from "../src/utils/oauth/index.ts";
import type { OAuthCredentials, OAuthProvider } from "../src/utils/oauth/types.ts";
const AUTH_PATH = join(homedir(), ".pi", "agent", "auth.json");
@@ -1,8 +1,8 @@
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { complete } from "../src/stream.js";
import type { Context } from "../src/types.js";
import { resolveApiKey } from "./oauth.js";
import { getModel } from "../src/models.ts";
import { complete } from "../src/stream.ts";
import type { Context } from "../src/types.ts";
import { resolveApiKey } from "./oauth.ts";
const codexToken = await resolveApiKey("openai-codex");
+1 -1
View File
@@ -1,5 +1,5 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { refreshOpenAICodexToken } from "../src/utils/oauth/openai-codex.js";
import { refreshOpenAICodexToken } from "../src/utils/oauth/openai-codex.ts";
describe("OpenAI Codex OAuth", () => {
afterEach(() => {
+20 -8
View File
@@ -7,8 +7,8 @@ import {
resetOpenAICodexWebSocketDebugStats,
streamOpenAICodexResponses,
streamSimpleOpenAICodexResponses,
} from "../src/providers/openai-codex-responses.js";
import type { Context, Model } from "../src/types.js";
} from "../src/providers/openai-codex-responses.ts";
import type { Context, Model } from "../src/types.ts";
const originalAgentDir = process.env.PI_CODING_AGENT_DIR;
@@ -173,7 +173,7 @@ describe("openai-codex streaming", () => {
messages: [{ role: "user", content: "Say hello", timestamp: Date.now() }],
};
const streamResult = streamOpenAICodexResponses(model, context, { apiKey: token });
const streamResult = streamOpenAICodexResponses(model, context, { apiKey: token, transport: "sse" });
let sawTextDelta = false;
let sawDone = false;
@@ -407,7 +407,7 @@ describe("openai-codex streaming", () => {
messages: [{ role: "user", content: "Say hello", timestamp: Date.now() }],
};
const streamResult = streamOpenAICodexResponses(model, context, { apiKey: token, sessionId });
const streamResult = streamOpenAICodexResponses(model, context, { apiKey: token, sessionId, transport: "sse" });
await streamResult.result();
});
@@ -513,7 +513,11 @@ describe("openai-codex streaming", () => {
messages: [{ role: "user", content: "Say hello", timestamp: Date.now() }],
};
await streamSimpleOpenAICodexResponses(model, context, { apiKey: token, reasoning: "xhigh" }).result();
await streamSimpleOpenAICodexResponses(model, context, {
apiKey: token,
reasoning: "xhigh",
transport: "sse",
}).result();
expect(requestedReasoning).toEqual({ effort: "xhigh", summary: "auto" });
});
@@ -559,6 +563,7 @@ describe("openai-codex streaming", () => {
})}`,
].join("\n\n")}\n\n`;
let requestedReasoning: unknown;
const encoder = new TextEncoder();
const stream = new ReadableStream<Uint8Array>({
start(controller) {
@@ -577,7 +582,7 @@ describe("openai-codex streaming", () => {
}
if (url === "https://chatgpt.com/backend-api/codex/responses") {
const body = typeof init?.body === "string" ? (JSON.parse(init.body) as Record<string, unknown>) : null;
expect(body?.reasoning).toEqual({ effort: "low", summary: "auto" });
requestedReasoning = body?.reasoning;
return new Response(stream, {
status: 200,
@@ -596,6 +601,7 @@ describe("openai-codex streaming", () => {
provider: "openai-codex",
baseUrl: "https://chatgpt.com/backend-api",
reasoning: true,
thinkingLevelMap: { minimal: "low" },
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 400000,
@@ -610,8 +616,10 @@ describe("openai-codex streaming", () => {
const streamResult = streamOpenAICodexResponses(model, context, {
apiKey: token,
reasoningEffort: "minimal",
transport: "sse",
});
await streamResult.result();
expect(requestedReasoning).toEqual({ effort: "low", summary: "auto" });
});
it.each([
@@ -701,7 +709,11 @@ describe("openai-codex streaming", () => {
messages: [{ role: "user", content: "Say hello", timestamp: Date.now() }],
};
const result = await streamOpenAICodexResponses(model, context, { apiKey: token, serviceTier }).result();
const result = await streamOpenAICodexResponses(model, context, {
apiKey: token,
serviceTier,
transport: "sse",
}).result();
expect(result.usage.cost.input).toBe(1 * multiplier);
expect(result.usage.cost.output).toBe(2 * multiplier);
@@ -801,7 +813,7 @@ describe("openai-codex streaming", () => {
};
// No sessionId provided
const streamResult = streamOpenAICodexResponses(model, context, { apiKey: token });
const streamResult = streamOpenAICodexResponses(model, context, { apiKey: token, transport: "sse" });
await streamResult.result();
});
it("forwards auto transport from streamSimple options and uses cached websocket context", async () => {
@@ -1,8 +1,8 @@
import { Type } from "typebox";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { getModel } from "../src/models.js";
import { streamOpenAICompletions } from "../src/providers/openai-completions.js";
import type { Model } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { streamOpenAICompletions } from "../src/providers/openai-completions.ts";
import type { Model } from "../src/types.ts";
interface CacheControl {
type: "ephemeral";
@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { getModel } from "../src/models.js";
import { streamSimple } from "../src/stream.js";
import { getModel } from "../src/models.ts";
import { streamSimple } from "../src/stream.ts";
// Empty tools arrays must NOT be serialized as `tools: []` — some OpenAI-compatible
// backends (e.g. DashScope / Aliyun Qwen via compatible-mode) reject the request with
@@ -1,7 +1,7 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { getModel } from "../src/models.js";
import { streamOpenAICompletions } from "../src/providers/openai-completions.js";
import type { Model } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { streamOpenAICompletions } from "../src/providers/openai-completions.ts";
import type { Model } from "../src/types.ts";
interface FakeOpenAIClientOptions {
apiKey: string;
@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { complete } from "../src/stream.js";
import type { Model } from "../src/types.js";
import { complete } from "../src/stream.ts";
import type { Model } from "../src/types.ts";
// Router/virtual ids (e.g. OpenRouter `auto`) keep `model` pinned to the
// requested id and surface the routed concrete id on `responseModel`.
@@ -2,7 +2,7 @@ import { once } from "node:events";
import http from "node:http";
import type { AddressInfo } from "node:net";
import { afterEach, describe, expect, it } from "vitest";
import { convertMessages, streamOpenAICompletions } from "../src/providers/openai-completions.js";
import { convertMessages, streamOpenAICompletions } from "../src/providers/openai-completions.ts";
import type {
AssistantMessage,
AssistantMessageEvent,
@@ -10,7 +10,7 @@ import type {
Model,
OpenAICompletionsCompat,
Usage,
} from "../src/types.js";
} from "../src/types.ts";
const emptyUsage: Usage = {
input: 0,
@@ -1,9 +1,9 @@
import { Type } from "typebox";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { getModel } from "../src/models.js";
import { convertMessages } from "../src/providers/openai-completions.js";
import { streamSimple } from "../src/stream.js";
import type { AssistantMessage, Model, Tool, ToolResultMessage } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { convertMessages } from "../src/providers/openai-completions.ts";
import { streamSimple } from "../src/stream.ts";
import type { AssistantMessage, Model, Tool, ToolResultMessage } from "../src/types.ts";
const mockState = vi.hoisted(() => ({
lastParams: undefined as unknown,
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { convertMessages } from "../src/providers/openai-completions.js";
import { getModel } from "../src/models.ts";
import { convertMessages } from "../src/providers/openai-completions.ts";
import type {
AssistantMessage,
Context,
@@ -8,7 +8,7 @@ import type {
OpenAICompletionsCompat,
ToolResultMessage,
Usage,
} from "../src/types.js";
} from "../src/types.ts";
const emptyUsage: Usage = {
input: 0,
@@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { complete } from "../src/stream.js";
import type { Context } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { complete } from "../src/stream.ts";
import type { Context } from "../src/types.ts";
describe.skipIf(!process.env.OPENAI_API_KEY)("openai responses cache affinity e2e", () => {
it("handles direct OpenAI Responses requests with aligned cache-affinity identifiers", { retry: 2 }, async () => {
@@ -1,7 +1,7 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { getModel } from "../src/models.js";
import { streamOpenAIResponses } from "../src/providers/openai-responses.js";
import type { Model } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { streamOpenAIResponses } from "../src/providers/openai-responses.ts";
import type { Model } from "../src/types.ts";
type CapturedHeaders = Headers | string[][] | Record<string, string | readonly string[]> | undefined;
@@ -1,8 +1,8 @@
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { convertResponsesMessages } from "../src/providers/openai-responses-shared.js";
import type { AssistantMessage, Context, ToolResultMessage, Usage } from "../src/types.js";
import { shortHash } from "../src/utils/hash.js";
import { getModel } from "../src/models.ts";
import { convertResponsesMessages } from "../src/providers/openai-responses-shared.ts";
import type { AssistantMessage, Context, ToolResultMessage, Usage } from "../src/types.ts";
import { shortHash } from "../src/utils/hash.ts";
const COPILOT_RAW_TOOL_CALL_ID =
"call_4VnzVawQXPB9MgYib7CiQFEY|I9b95oN1wD/cHXKTw3PpRkL6KkCtzTJhUxMouMWYwHeTo2j3htzfSk7YPx2vifiIM4g3A8XXyOj8q4Bt6SLUG7gqY1E3ELkrkVQNHglRfUmWj84lqxJY+Puieb3VKyX0FB+83TUzn91cDMF/4gzt990IzqVrc+nIb9RRscRD070Du16q1glydVjWR0SBJsE6TbY/esOjFpqplogQqrajm1eI++f3eLi73R6q7hVusY0QbeFySVxABCjhN0lXB04caBe1rzHjYzul6MAXj7uq+0r17VLq+yrtyYhN12wkmFqHeqTyEei6EFPbMy24Nc+IbJlkP0OCg02W+gOnyBFcbi2ctvJFSOhSjt1CqBdqCnnhwUqXjbWiT0wh3DmLScRgTHmGkaI+oAcQQjfic65nxj+TnEkReA==";
@@ -1,8 +1,8 @@
import type { ResponseStreamEvent } from "openai/resources/responses/responses.js";
import { describe, expect, it, vi } from "vitest";
import { processResponsesStream } from "../src/providers/openai-responses-shared.js";
import type { AssistantMessage, AssistantMessageEvent, Model } from "../src/types.js";
import { AssistantMessageEventStream } from "../src/utils/event-stream.js";
import { processResponsesStream } from "../src/providers/openai-responses-shared.ts";
import type { AssistantMessage, AssistantMessageEvent, Model } from "../src/types.ts";
import { AssistantMessageEventStream } from "../src/utils/event-stream.ts";
function createOutput(model: Model<"openai-responses">): AssistantMessage {
return {
@@ -1,8 +1,8 @@
import { Type } from "typebox";
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { complete, getEnvApiKey } from "../src/stream.js";
import type { AssistantMessage, Context, Message, Tool, ToolCall } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { complete, getEnvApiKey } from "../src/stream.ts";
import type { AssistantMessage, Context, Message, Tool, ToolCall } from "../src/types.ts";
const testToolSchema = Type.Object({
value: Type.Number({ description: "A number to double" }),
@@ -4,10 +4,10 @@ import { fileURLToPath } from "node:url";
import type { ResponseFunctionCallOutputItemList } from "openai/resources/responses/responses.js";
import { Type } from "typebox";
import { describe, expect, it } from "vitest";
import type { Api, Context, Model, StreamOptions, Tool, ToolResultMessage } from "../src/index.js";
import { complete, getModel } from "../src/index.js";
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.js";
import { resolveApiKey } from "./oauth.js";
import type { Api, Context, Model, StreamOptions, Tool, ToolResultMessage } from "../src/index.ts";
import { complete, getModel } from "../src/index.ts";
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.ts";
import { resolveApiKey } from "./oauth.ts";
type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { completeSimple } from "../src/stream.js";
import { getModel } from "../src/models.ts";
import { completeSimple } from "../src/stream.ts";
function createLongSystemPrompt(): string {
const nonce = `${Date.now()}-${Math.random()}`;
+2 -2
View File
@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { generateImages } from "../src/images.js";
import type { ImagesContext, ImagesModel } from "../src/types.js";
import { generateImages } from "../src/images.ts";
import type { ImagesContext, ImagesModel } from "../src/types.ts";
const mockState = vi.hoisted(() => ({
lastParams: undefined as unknown,
+2 -2
View File
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import type { AssistantMessage } from "../src/types.js";
import { isContextOverflow } from "../src/utils/overflow.js";
import type { AssistantMessage } from "../src/types.ts";
import { isContextOverflow } from "../src/utils/overflow.ts";
function createErrorMessage(errorMessage: string): AssistantMessage {
return {
+5 -5
View File
@@ -1,9 +1,9 @@
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { complete } from "../src/stream.js";
import type { Api, Context, Model, StreamOptions } from "../src/types.js";
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.js";
import { resolveApiKey } from "./oauth.js";
import { getModel } from "../src/models.ts";
import { complete } from "../src/stream.ts";
import type { Api, Context, Model, StreamOptions } from "../src/types.ts";
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.ts";
import { resolveApiKey } from "./oauth.ts";
type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
+8 -8
View File
@@ -4,17 +4,17 @@ import { dirname, join } from "path";
import { Type } from "typebox";
import { fileURLToPath } from "url";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { complete, stream } from "../src/stream.js";
import type { Api, Context, ImageContent, Model, StreamOptions, Tool, ToolResultMessage } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { complete, stream } from "../src/stream.ts";
import type { Api, Context, ImageContent, Model, StreamOptions, Tool, ToolResultMessage } from "../src/types.ts";
type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
import { StringEnum } from "../src/utils/typebox-helpers.js";
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.js";
import { hasBedrockCredentials } from "./bedrock-utils.js";
import { hasCloudflareAiGatewayCredentials, hasCloudflareWorkersAICredentials } from "./cloudflare-utils.js";
import { resolveApiKey } from "./oauth.js";
import { StringEnum } from "../src/utils/typebox-helpers.ts";
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.ts";
import { hasBedrockCredentials } from "./bedrock-utils.ts";
import { hasCloudflareAiGatewayCredentials, hasCloudflareWorkersAICredentials } from "./cloudflare-utils.ts";
import { resolveApiKey } from "./oauth.ts";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
+1 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { getModel, getSupportedThinkingLevels } from "../src/models.js";
import { getModel, getSupportedThinkingLevels } from "../src/models.ts";
describe("getSupportedThinkingLevels", () => {
it("includes xhigh for Anthropic Opus 4.6 on anthropic-messages API", () => {
+2 -2
View File
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest";
import { findEnvKeys, getEnvApiKey } from "../src/env-api-keys.js";
import { getModel } from "../src/models.js";
import { findEnvKeys, getEnvApiKey } from "../src/env-api-keys.ts";
import { getModel } from "../src/models.ts";
const originalTogetherApiKey = process.env.TOGETHER_API_KEY;
+7 -7
View File
@@ -1,14 +1,14 @@
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { stream } from "../src/stream.js";
import type { Api, Context, Model, StreamOptions } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { stream } from "../src/stream.ts";
import type { Api, Context, Model, StreamOptions } from "../src/types.ts";
type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.js";
import { hasBedrockCredentials } from "./bedrock-utils.js";
import { hasCloudflareAiGatewayCredentials, hasCloudflareWorkersAICredentials } from "./cloudflare-utils.js";
import { resolveApiKey } from "./oauth.js";
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.ts";
import { hasBedrockCredentials } from "./bedrock-utils.ts";
import { hasCloudflareAiGatewayCredentials, hasCloudflareWorkersAICredentials } from "./cloudflare-utils.ts";
import { resolveApiKey } from "./oauth.ts";
// Resolve OAuth tokens at module level (async, runs before tests)
const oauthTokens = await Promise.all([
@@ -12,10 +12,10 @@
import { Type } from "typebox";
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { completeSimple, getEnvApiKey } from "../src/stream.js";
import type { AssistantMessage, Message, Tool, ToolResultMessage } from "../src/types.js";
import { resolveApiKey } from "./oauth.js";
import { getModel } from "../src/models.ts";
import { completeSimple, getEnvApiKey } from "../src/stream.ts";
import type { AssistantMessage, Message, Tool, ToolResultMessage } from "../src/types.ts";
import { resolveApiKey } from "./oauth.ts";
// Resolve API keys
const copilotToken = await resolveApiKey("github-copilot");
@@ -1,15 +1,15 @@
import { Type } from "typebox";
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { complete } from "../src/stream.js";
import type { Api, Context, Model, StreamOptions, Tool } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { complete } from "../src/stream.ts";
import type { Api, Context, Model, StreamOptions, Tool } from "../src/types.ts";
type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.js";
import { hasBedrockCredentials } from "./bedrock-utils.js";
import { hasCloudflareAiGatewayCredentials, hasCloudflareWorkersAICredentials } from "./cloudflare-utils.js";
import { resolveApiKey } from "./oauth.js";
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.ts";
import { hasBedrockCredentials } from "./bedrock-utils.ts";
import { hasCloudflareAiGatewayCredentials, hasCloudflareWorkersAICredentials } from "./cloudflare-utils.ts";
import { resolveApiKey } from "./oauth.ts";
// Resolve OAuth tokens at module level (async, runs before tests)
const oauthTokens = await Promise.all([
+7 -7
View File
@@ -13,16 +13,16 @@
*/
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { complete } from "../src/stream.js";
import type { Api, Context, Model, StreamOptions, Usage } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { complete } from "../src/stream.ts";
import type { Api, Context, Model, StreamOptions, Usage } from "../src/types.ts";
type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.js";
import { hasBedrockCredentials } from "./bedrock-utils.js";
import { hasCloudflareAiGatewayCredentials, hasCloudflareWorkersAICredentials } from "./cloudflare-utils.js";
import { resolveApiKey } from "./oauth.js";
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.ts";
import { hasBedrockCredentials } from "./bedrock-utils.ts";
import { hasCloudflareAiGatewayCredentials, hasCloudflareWorkersAICredentials } from "./cloudflare-utils.ts";
import { resolveApiKey } from "./oauth.ts";
// Resolve OAuth tokens at module level (async, runs before tests)
const oauthTokens = await Promise.all([
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { transformMessages } from "../src/providers/transform-messages.js";
import type { AssistantMessage, Message, Model, ToolCall } from "../src/types.js";
import { transformMessages } from "../src/providers/transform-messages.ts";
import type { AssistantMessage, Message, Model, ToolCall } from "../src/types.ts";
// Normalize function matching what anthropic.ts uses
function anthropicNormalizeToolCallId(
+7 -7
View File
@@ -1,15 +1,15 @@
import { Type } from "typebox";
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { complete } from "../src/stream.js";
import type { Api, Context, Model, StreamOptions, ToolResultMessage } from "../src/types.js";
import { getModel } from "../src/models.ts";
import { complete } from "../src/stream.ts";
import type { Api, Context, Model, StreamOptions, ToolResultMessage } from "../src/types.ts";
type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.js";
import { hasBedrockCredentials } from "./bedrock-utils.js";
import { hasCloudflareAiGatewayCredentials, hasCloudflareWorkersAICredentials } from "./cloudflare-utils.js";
import { resolveApiKey } from "./oauth.js";
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.ts";
import { hasBedrockCredentials } from "./bedrock-utils.ts";
import { hasCloudflareAiGatewayCredentials, hasCloudflareWorkersAICredentials } from "./cloudflare-utils.ts";
import { resolveApiKey } from "./oauth.ts";
// Empty schema for test tools - must be proper OBJECT type for Cloud Code Assist
const emptySchema = Type.Object({});
+2 -2
View File
@@ -1,7 +1,7 @@
import { Type } from "typebox";
import { describe, expect, it } from "vitest";
import type { Tool, ToolCall } from "../src/types.js";
import { validateToolArguments } from "../src/utils/validation.js";
import type { Tool, ToolCall } from "../src/types.ts";
import { validateToolArguments } from "../src/utils/validation.ts";
function createToolCallWithPlainSchema(
schema: Tool["parameters"],

Some files were not shown because too many files have changed in this diff Show More