Skip to content

feat(coding-agent): credential broker with pooled selection and background refresh#194

Open
islee23520 wants to merge 36 commits into
code-yeongyu:mainfrom
islee23520:feat/auth-credential-broker
Open

feat(coding-agent): credential broker with pooled selection and background refresh#194
islee23520 wants to merge 36 commits into
code-yeongyu:mainfrom
islee23520:feat/auth-credential-broker

Conversation

@islee23520

@islee23520 islee23520 commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Loopback credential broker that stores OAuth/API-key credentials in a SQLite vault and leases them to authorized gateway clients:

  • auth-multi-account / credential-selection / in-memory-credential-vault: multi-account credential contracts, pooled selection, in-memory vault.
  • auth-broker + auth-broker-wire-contract: broker service with CAS selection leases, redacting wire contract, remote store.
  • auth-broker-cli + auth-broker-server: senpi auth-broker CLI (serve, token, login, logout, import, backup, restore, migrate) over loopback HTTP.
  • auth-broker-refresher: background OAuth refresh loop (5 min skew / 60 s cadence) — renews expiring tokens, disables definitive failures.
  • model-registry / sdk: pooled credential selection + outcome reporting wired into the agent loop; index exports the credential surface; main registers the command.

Stack

Stacked on the provider-parity PR (depends on its KnownProvider union). This branch includes that PR's commit; merge after it, then rebase to drop it.

Evidence

  • npm run check green.
  • broker + multi-account + model-registry tests: 117 passing.

Summary by cubic

Adds a local credential broker that stores OAuth/API keys, leases them with pooled selection, and auto-refreshes for the agent and gateway. Finalizes login parity with Cursor Connect, Google CCA, and clearer provider login rules; updates dev env secrets for new API‑key providers.

  • New Features

    • Broker: SQLite vault + loopback HTTP; CAS leases; redacted contract; remote‑store sessions.
    • CLI: senpi auth-broker (serve, token, login, logout, import, backup, restore, migrate).
    • Auto‑refresh: 60s cadence with 5m skew; renews expiring tokens; suppresses hard failures.
    • Agent: pooled selection and outcome reporting via model-registry/sdk; command registered in main.
    • Transports: Cursor Connect; Google CCA (google-gemini-cli/Antigravity) with SSE and project discovery.
    • Providers/OAuth: OAuth for Cursor, GitHub Copilot, GitLab Duo (direct‑access), Kilo, OpenAI Codex (device), xAI; adds API‑key providers including ollama/ollama-cloud, lm-studio, litellm, deepinfra, moonshot, qwen-portal, perplexity, venice, vllm, and others; removes search‑only providers.
    • Dev env: .devcontainer secrets and docs updated for new provider keys.
  • Bug Fixes

    • Broker: CAS‑disable matches credential_id + updatedAt + material; preserve OAuth extras (e.g., projectId); prune expired leases; count pools in hasAuth/getAuthStatus; optional‑chain credentialVault.
    • OAuth/protocol: request‑scoped auth via getRequestAuth/getApiKey; CRLF‑delimited SSE parsing; GitLab PAT exchange + direct‑access headers; route GitLab GPT‑5.1 via chat‑completions; delist Perplexity session OAuth from /login; restore oauth: "radius" in models.json.
    • xAI/Google: restore device‑code xAI OAuth (compat xaiOAuthProvider); drop duplicate Google provider registration; keep Google CCA providers OAuth‑only for /login.
    • Login parity: show display names for minimax-code/moonshot/opencode-zen; exclude OAuth‑only providers from API‑key login (openai-codex-device, Google CCA); store openai-codex-device creds under the openai-codex key; unify Kimi login to kimi-coding.
    • Tests: stabilize app‑server idle‑unload timers; tolerate wrapped paths in compact tool‑execution render; normalize multi‑account suite formatting.

Written for commit ed7407a. Summary will update on new commits.

Review in cubic

Add 18 API-key providers (alibaba-coding-plan, cursor, deepinfra, firepass,
fugu, gitlab-duo, glm-zcode, kagi, kilo, kimi-code, litellm, lm-studio,
minimax-code(-cn), moonshot, nanogpt, ollama(-cloud), openai-codex-device,
parallel, perplexity, qianfan, qwen-portal, synthetic, tavily, venice, vllm,
zenmux) plus OAuth flows for cursor, gitlab-duo, glm-zcode, google-antigravity,
google-gemini-cli, kilo, kimi-code, openai-codex-device, perplexity, xai, and
the remaining Gajae provider-ID gaps for full /login parity.

The coding-agent provider wiring (model-resolver defaults, provider-display-names,
auth-providers oauth-only set) travels here because model-resolver is exhaustive
over the ai KnownProvider union. Source of truth for keys remains env-api-keys.ts;
parity is enforced by test/api-key-provider-parity.test.ts.
…ackground refresh

Add a loopback credential broker that stores OAuth/API-key credentials in a
SQLite vault and leases them to authorized gateway clients:

- auth-multi-account / credential-selection / in-memory-credential-vault:
  multi-account credential contracts, pooled selection, and an in-memory vault.
- auth-broker + wire-contract: broker service with CAS selection leases,
  redacting wire contract, and a remote store for clients.
- auth-broker-cli + auth-broker-server: `senpi auth-broker` CLI (serve, token,
  login, logout, import, backup, restore, migrate) over a loopback HTTP server.
- auth-broker-refresher: background OAuth refresh loop (5 min skew / 60 s
  cadence) that renews expiring tokens and disables definitive failures.
- model-registry / sdk: wire pooled credential selection and outcome reporting
  into the agent loop; index exports the credential surface; main registers the
  `auth-broker` command.

Stacked on the provider-parity branch (depends on its KnownProvider union).
Declare the API-key providers added for /login parity (alibaba-coding-plan,
deepinfra, firepass, fugu, kagi, litellm, lm-studio, nanogpt, ollama,
ollama-cloud, parallel, qianfan, qwen-portal, synthetic, tavily, venice,
vllm, zenmux) across the three synchronized environment surfaces:
.devcontainer/devcontainer.json secrets, scripts/devenv-setup.mjs PROVIDER_KEYS,
and .agents/skills/senpi-qa/references/env-vars.md. Source of truth remains
packages/ai/src/env-api-keys.ts.
…ause, migration writes

- Harden auth-broker-refresher with improved credential refresh logic
- Add restore lease support and remote-store session handling
- Add refresher-start and remote-store-session test coverage
- Update auth-broker-wire-contract with additional contract checks
Resolve changes.md conflict by keeping both the credential broker
section and the upstream compaction/extension sections.
- Remove kagi, parallel, and tavily providers (search-only, no chat)
- Fix GLM ZCode OAuth to pin endpoints to z.ai
- Add CRLF-delimited SSE parsing for provider streams
- Add GitLab Duo and Perplexity OAuth test coverage
- Update model-registry and model-resolver for removed providers
Resolve changes.md conflicts in packages/ai and packages/coding-agent
by keeping both the provider parity sections and the upstream sections.
Resolve provider-union conflicts with main Radius support while keeping
parity providers. Drop residual kagi/tavily/parallel env-surface wiring.
Cascade provider-parity + Radius onto the broker branch. Keep getRequestAuth
and pooled credential selection. Drop residual kagi/tavily/parallel providers.
@islee23520

islee23520 commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

@code-yeongyu This PR's CI is blocked before any jobs start: the fork workflow run concluded action_required, so the required Check and test context remains “Expected — Waiting for status to be reported.”

Could you open the run below and choose Approve and run workflows?

https://github.com/code-yeongyu/senpi/actions/runs/29494565503

No branch-protection or workflow change is requested; this only approves the existing run for this PR head SHA.

@code-yeongyu code-yeongyu left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review verdict: NEEDS-CHANGES

The broker core (SQLite vault schema, parameter-bound SQL, loopback allowlist, 0600/0700 perms, redacting wire contract) is a good base, but the increment has correctness gaps in exactly the paths the broker exists for: pooled OAuth, refresh-vs-relogin races, and provider-specific credentials. The auth-storage/model-registry/sdk wiring also predates main's model-runtime refactor (9993c9690) and must be redesigned against CredentialStore/ModelRuntime.

Findings

Pri Location Finding
P1 packages/coding-agent/src/core/auth-broker.ts:433 CAS-guard refresher disables against the loaded snapshot — The sweep loads a credential, awaits its refresh, and then disables by credential_id alone on a definitive failure. Because the identity-conflict upsert preserves the existing credential_id, a user re-login that rotates material while the old refresh is in flight is still matched and the fresh credential is disabled; the catch only helps if the row disappeared.
P1 packages/coding-agent/src/core/auth-storage.ts:627 Resolve pooled OAuth through the provider auth adapter — selectionFromLease treats every OAuth access token as the provider API key. For google-gemini-cli, the provider adapter serializes { token, projectId } as the API key, and providers may also require request-specific headers, so a pooled OAuth selection bypasses required auth transformation and produces failing requests.
P1 packages/coding-agent/src/cli/auth-broker-cli.ts:335 Preserve provider-specific OAuth fields in the vault — The broker login narrows OAuthCredentials to access, expiry, and refresh token, discarding provider-specific fields. Google login returns projectId and its refresh implementation requires that field, while the CLIProxy import also accepts project_id but does not retain it; login/import therefore succeeds but the next refresh cannot be performed and request auth cannot be reconstructed.
P1 packages/coding-agent/src/core/auth-storage.ts:392 Count pooled credentials as configured auth — setCredentialVault can make a pool the only available credential source, but hasAuth still checks only runtime overrides, auth.json, and environment variables. ModelRegistry.getAvailable() and initial-model selection route through this method, so a pool-only setup is reported as having no usable models unless the user forces an explicit model, even though getApiKeyAndHeaders can select the credential.
P2 packages/coding-agent/src/cli/auth-broker-cli.ts:249 Normalize login-only provider aliases before storing pools — Broker login stores the selected provider ID directly in the credential pool. Logging in through the supported openai-codex-device alias therefore creates an openai-codex-device pool, while request-side selection normalizes the model provider to openai-codex and never finds it.
P1 packages/coding-agent/src/core/auth-broker.ts:84 Redact imported disable causes before metadata exposure — upsertCredential writes record.disabled.cause verbatim, whereas redaction is applied only by the explicit disable method. Restore/import accepts caller-controlled disabled-state causes and metadataSnapshot returns them, so a backup or CLIProxy record containing a token in its cause bypasses redactDisableCause and exposes it to metadata-read clients.
P2 packages/coding-agent/src/core/auth-broker.ts:132 Expire and prune persisted selection leases — Every selection inserts a lease row, but consume and outcome reporting only update it; there is no issued time, expiry, or deletion. Normal broker traffic therefore grows the SQLite database by one permanent row per model request, and an arbitrarily old consumed lease can submit its first unauthorized or rate_limited outcome later, at which point cooldown is calculated from the current time.
P1 packages/coding-agent/src/core/model-registry.ts:680 Retain custom Radius registration from the parent PR — This increment removes the parent PR's oauth: "radius" schema and registration path, then treats every no-model provider as an ordinary override. Existing custom Radius gateway configurations consequently stop registering their OAuth provider and dynamic model catalog, regressing users who do not enable the broker.

Merge path

  1. Re-port the wire contract, vault, refresher, and server core onto current main; redesign the agent-loop integration against ModelRuntime instead of the replaced registry surfaces.
  2. Fix the four P1 credential-correctness bugs first (CAS disable race, pooled OAuth via provider getRequestAuth/getApiKey, provider-specific fields like projectId surviving the vault round-trip, hasAuth counting pools).
  3. Note the vault DB/WAL files rely on directory perms only — consider explicit 0600 on the SQLite files too.

(Reviewed with the PR refs fetched locally against origin/main; line numbers refer to the PR head.)

… P1s, drop GLM ZCode

Re-port OAuth flows from utils/oauth to auth/oauth with bundledLoaders,
route Cursor through Connect/protobuf ChatService, fix GitLab PAT exchange
and gpt-5.1 chat-completions routing, delist Perplexity session OAuth from
/login, exclude Google CCA providers from API-key login, remove unofficial
GLM ZCode login, keep browser smoke Node-free, and update stale kimi-coding
live-test model ids.
…ndings

CAS-guard OAuth disable against updatedAt snapshots, preserve OAuth extras
(projectId) through vault round-trips, resolve pooled OAuth via provider
getRequestAuth/getApiKey, count pools in hasAuth, redact disabled.cause on
upsert/import, prune expired leases, restore models.json oauth:"radius"
registration, and normalize broker login provider aliases.
@islee23520

Copy link
Copy Markdown
Contributor Author

Review follow-up (P1/P2)

Addressed the CHANGES_REQUESTED findings on feat/auth-credential-broker (b20e61ed8).

Fixes

Pri Finding Fix
P1 CAS disable race (refresh vs re-login) disableCredentialIfUnchanged(credentialId, expectedUpdatedAt, cause); sweep uses snapshot updatedAt
P1 pooled OAuth bypasses provider auth selectPooledCredential is async and routes OAuth through getRequestAuth / getApiKey, including headers
P1 projectId lost in vault OAuthCredentialMaterial.extras + parse/CLI login/refresh preserve non-core OAuth fields
P1 hasAuth ignores pools pool-only credentials count as configured auth
P1 disabled.cause not redacted on import upsertCredential always runs redactDisableCause
P1 oauth: "radius" models.json restored ProviderConfigSchema.oauth + registerCustomRadiusOAuthProvider on load
P2 alias normalization broker login stores resolveOAuthStorageProvider(providerId)
P2 lease growth leases get issued_at/expires_at (15m), prune on issue, reject expired consume

Evidence

vitest packages/coding-agent:
  test/suite/auth-broker-review-p1.test.ts
  test/suite/auth-broker-refresh-cas.test.ts
  test/suite/auth-multi-account.test.ts
→ 3 files / 17 tests PASS
tsgo --noEmit → exit 0 (scoped clean)

Residual / out of scope this commit

  • Full re-port onto main ModelRuntime/CredentialStore end-state still pending a main merge/rebase (branch remains stacked on provider-parity).
  • Lease outcome cooldown age for ancient consumed leases is mitigated by prune + expiry reject; further outcome-time anchoring can follow if needed.

Bring PR code-yeongyu#193 onto current main auth/ModelRuntime layout while preserving
provider OAuth parity review fixes (Cursor Connect, GitLab, Perplexity
delist, Google OAuth-only, GLM ZCode removed) and regenerating model catalogs.
… main rebase

Rebase the broker branch onto the conflict-resolved code-yeongyu#193/main auth stack,
re-port multi-account vault hooks onto CredentialStore AuthStorage, and keep
broker review P1/P2 fixes (CAS disable, extras, lease prune, getRequestAuth).
@gitguardian

gitguardian Bot commented Jul 18, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

Since your pull request originates from a forked repository, GitGuardian is not able to associate the secrets uncovered with secret incidents on your GitGuardian dashboard.
Skipping this check run and merging your pull request will create secret incidents on your GitGuardian dashboard.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
34886147 Triggered Generic High Entropy Secret a7648c9 packages/ai/scripts/generate-models.ts View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

islee23520 added a commit to islee23520/senpi that referenced this pull request Jul 18, 2026
…in rebase

Bring the gateway branch onto the conflict-resolved code-yeongyu#193/code-yeongyu#194 stack and keep
auth-gateway review P1/P2 fixes (broker URL validation, CORS preflight order,
verifier evidence, 502 mapping, qualified models).
@islee23520

Copy link
Copy Markdown
Contributor Author

Conflict resolution (stack)

Merged conflict-resolved feat/auth-provider-parity (post-main) into this branch and re-ported multi-account vault hooks onto main’s CredentialStore AuthStorage.

  • Keeps broker review P1/P2 (CAS disable, extras/projectId, lease prune, pooled getRequestAuth, hasAuth pools, redaction, radius oauth, alias normalize).
  • tsgo --noEmit clean; broker review tests green after list() shape align.
  • Pushed latest HEAD for re-check.

…er-parity

PR base is upstream main (not the fork). Merge upstream main and keep
provider/OAuth parity review fixes while accepting upstream model catalogs.
Drop the PR's xAI browser OAuth in favor of main's device-code flow, restore
OAuth-only API-key login exclusions for Google CCA providers, and keep the
provider/OAuth parity work on current main.
Keep main's device-code xAI flow (drop PR browser discovery), add a thin
legacy OAuthProviderInterface adapter for the compat registry, and exclude
google-gemini-cli / google-antigravity from API-key /login.
Integrate main's ModelRuntime/video/bash abort changes with the credential
broker branch. Keep both change logs (main video/truncation + broker/OAuth).
CAS-disable now matches credential_id + updatedAt + material so a concurrent
re-login cannot be disabled by a stale refresh. Preserve CLIProxy/Gajae
projectId extras through import, count pools in hasAuth/getAuthStatus, and
use optional chaining for credentialVault (biome useOptionalChain).
@islee23520

Copy link
Copy Markdown
Contributor Author

Babysit update (post-review)

Integrated current main (ModelRuntime/video/bash-abort stack) and hardened remaining review findings on the broker path.

Integration

  • Merged origin/main into feat/auth-credential-broker (merge preferred over rebase due to merge-heavy history / OAuth path rename conflicts).
  • Conflict resolution limited to packages/ai/src/changes.md and packages/coding-agent/src/core/changes.md (kept both main video/truncation notes and broker/OAuth notes).
  • Custom Radius oauth: "radius" remains on main via ModelRuntime.configureRadiusProviders() — no separate model-registry re-registration needed after the CredentialStore refactor.

Review P1 fixes (this push + prior b20e61ed8)

  1. CAS-guard refresher disable (auth-broker.ts): disableCredentialIfUnchanged now matches credential_id + updatedAt and material (semantic equality in a transaction). Stale refresh after re-login that preserves identity/credential_id cannot disable the rotated credential.
  2. Pooled OAuth via provider adapter (auth-storage.ts selectionFromLease): still routes OAuth leases through getRequestAuth / getApiKey with vault extras (e.g. projectId).
  3. Provider-specific OAuth fields: login already preserved extras; import now maps CLIProxy project_id / Gajae projectId into vault material.extras.
  4. hasAuth / getAuthStatus count pools: pool-only setups report configured auth (credential-pool label) so model availability does not force an explicit model.
  5. Redact imported disable causes: upsertCredential continues to run redactDisableCause on disabled.cause.
  6. Biome useOptionalChain: credentialVault?.metadataSnapshot()... in hasAuth / getAuthStatus.

P2 already present / retained

  • Login normalizes via resolveOAuthStorageProvider (openai-codex-device → storage provider).
  • Lease expiry + pruneExpiredLeases retained.

Verification

  • npx biome check --error-on-warnings on touched broker files: clean
  • Vitest: auth-broker-review-p1, auth-broker-import, auth-broker-refresh-cas, auth-broker, auth-broker-refresher, auth-broker-command, auth-multi-account38/38 passed

Head: 59d7f3015

builtinProviders listed googleProvider() twice, so Models.setProvider
collapsed to 63 unique providers while the array length was 64 and
providers.test failed. Matches main's single registration.
- Remove double googleProvider() so builtinModels length matches unique IDs
- Replace browser/discovery xAI OAuth with main's device-code flow plus
  compat xaiOAuthProvider adapter for the legacy registry
@islee23520

Copy link
Copy Markdown
Contributor Author

Evidence-based follow-up (CI green path)

Review P1 verification

Finding Verdict
CAS disable race FixeddisableCredentialIfUnchanged matches material + updatedAt
Pooled OAuth via getRequestAuth FixedselectionFromLease
Preserve projectId extras Fixed — vault/import/login retain extras
hasAuth counts pools Fixed
Redact disable causes on upsert Fixed
Biome optional chain Fixed earlier

CI / integration this push

Tests run locally

  • packages/ai providers + xai: 31/31
  • packages/coding-agent auth-broker suite (13 files): 51/51
  • biome on touched files: clean

Full monorepo CLI integration tests that need workspace package dist links can fail in bare worktrees; CI is the authority for those. Not merging until green CI + re-review.

Expose minimax-code, minimax-code-cn, moonshot, and opencode-zen as
API-key login targets via display-name eligibility.
- Add display names so minimax-code/moonshot/opencode-zen appear in /login
- Exclude oauth-only providers from API-key login (codex-device, google CCA)
- Store openai-codex-device credentials under canonical openai-codex key
- Align gajae-login-provider-parity tests with async list() API
@islee23520

Copy link
Copy Markdown
Contributor Author

Evidence cycle 2

Same Gajae login + codex-device storage alias fixes as #195 (shared stack surface), so CI does not hit the same login-parity failures when Check and test reaches those tests.

Local: gajae-login-provider-parity 2/2, auth-broker suite 51/51, senpi-qa self-tests pass.

Not merging until green CI + re-review.

…render

Long absolute worktree paths wrap at render width 120, so compact
collapsed text is no longer a single line. Flatten whitespace before
asserting the compact resource label.
…render

Long absolute worktree paths wrap at render width 120, so compact
collapsed text is no longer a single line. Flatten whitespace before
asserting the compact resource label. Also normalize multi-account
suite formatting for biome.
Build harness under real timers and enable fake timers only for the
idle-unload countdown so Vitest no longer flakes with 30s hangs on
advanceTimersByTimeAsync/runOnlyPendingTimersAsync in CI.
Build harness under real timers and enable fake timers only for the
idle-unload countdown so Vitest no longer flakes with 30s hangs on
advanceTimersByTimeAsync/runOnlyPendingTimersAsync in CI.
@islee23520

Copy link
Copy Markdown
Contributor Author

CI fix: app-server idle-unload timer flake

Root cause

Check and test was flaking on:

  • app-server-thread-handlers-catch.test.ts — idle unload registry error
  • app-server-thread-handlers-cold.test.ts — idle unload / transport-close / dispose timer cases

Those tests called vi.useFakeTimers() before async harness setup (createHarness / thread/start). Under load, Vitest fake timers could hang advanceTimersByTimeAsync / runOnlyPendingTimersAsync until the 30s suite timeout.

Fix (head 1609f7d2c1)

  • Build harness under real timers
  • Enable fake timers only for the idle-unload countdown (toFake: ["setTimeout","clearTimeout"])
  • Prefer advanceTimersByTimeAsync(0) for the zero-delay unload case

Local evidence

  • Targeted suite: 25/25 stress passes on main deps; 5/5 per PR worktree
  • Full packages/coding-agent vitest with the fix: 3781 passed | 31 skipped (432 files)
  • Pre-commit npm run check green on commit (PR feat(ai): provider and OAuth parity for /login #193 worktree)

Still open / not claimed closed

Please re-run review after CI is green on this head.

kimi-code and kimi-coding are the same product at api.kimi.com/coding with
the same KIMI_API_KEY. Upstream kimi-coding is the maintained entry
(k3 video input, adaptive thinking), so drop the PR-only kimi-code OAuth
provider and its catalog so /login shows exactly one Kimi entry.
Adds a regression asserting a single kimi-* provider and KIMI_API_KEY
resolution.
…parity

# Conflicts:
#	packages/ai/src/providers/data/openrouter.json
…feat/auth-credential-broker

# Conflicts:
#	package-lock.json
#	packages/ai/src/changes.md
#	packages/coding-agent/src/core/auth-providers.ts
#	packages/coding-agent/src/core/changes.md
…parity

# Conflicts:
#	packages/ai/test/abort.test.ts
#	packages/ai/test/context-overflow.test.ts
#	packages/ai/test/cross-provider-handoff.test.ts
#	packages/ai/test/empty.test.ts
#	packages/ai/test/image-tool-result.test.ts
#	packages/ai/test/stream.test.ts
#	packages/ai/test/tokens.test.ts
#	packages/ai/test/tool-call-without-result.test.ts
#	packages/ai/test/total-tokens.test.ts
#	packages/ai/test/unicode-surrogate.test.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants