Skip to content

Commit 772e69a

Browse files
authored
refactor(agent-core-v2): remove the legacy kosong request layer (#352)
## Requirement or Bug Remove the legacy `packages/agent-core-v2/src/kosong` request layer. Port the features that existed only there and fix the bugs it caused. Stacked on #349. ## Bug Reproduction Steps 1. Configure a provider with `type = "openai"` and `env = { OPENAI_API_KEY = "sk-..." }`, with no `apiKey` and no process env key. 2. Start a session and send a turn. 3. The auth check fails with `AuthTokenMissingError`, even though the request path would find the key. ## Root Cause 20 of the 62 files under `src/kosong` still loaded at runtime through `app/auth/*` and `app/kosongConfig/*`. kosong keeps its own provider-definition map, and at runtime that map held only the pythinker definitions, because the standard definitions never loaded. So `resolveModelAuthMaterial` / `resolveModelForReady` (auth check) and `envOverlay` (vendor `*_BASE_URL`) ignored provider-`env` values for every non-pythinker provider. Real requests use `llm-adapter` and find them. This is a fundamental fix: one provider-definition registry, the live one. ## Code Changes - Delete `src/kosong` (61 files, about 11k lines). The 19 importers now import the same symbols from `#/llm-adapter/*` (types are identical apart from the import path). - Port features that existed only in the deleted copy, each with a test that fails before and passes after: - **OpenCode billing errors:** a 401/402/403 whose body says "insufficient balance", "insufficient credit", "credits exhausted" or "please recharge" is a provider error, not `provider.auth_error`. Ordinary 401s stay auth errors. - **DSML / Hermes tool calls:** tool calls that some models write as text tags on the chat-completions stream are parsed into real tool calls. - **`modelRecordProviderId`** moved to `llm-adapter/model/model.ts`. - `apps/vis` imports `@pymodel/agent-core-v2/llm-adapter/contract/tokens` instead of the `kosong` subpath. - `scripts/check-identity-freeze.mjs` drops the deleted kosong path. - Test fix: an MCP registry test set the wrong home variable, so it did not isolate the home directory. It now sets `PYTHINKER_CODE_HOME`. The default-model fallback that also lived only in kosong is **not** restored: since #323 the gateway tests require that `default_model` is never rewritten. That is a product decision, tracked in #351. ## Behavior Changes and Affected Users | Behavior | Before | After | Who relies on the old behavior | Escape hatch | |---|---|---|---|---| | Vendor API key in a provider's `env` table | Auth check ignores it, turn fails with `AuthTokenMissingError` | Key is found, turn runs | Nobody (the old behavior was a bug) | n/a | | Vendor `*_BASE_URL` in an `env` provider of non-pythinker type | Ignored by `envOverlay` | Applied | Nobody (bug) | Remove the variable from `env` | | OpenCode 401/402/403 with a billing message | `provider.auth_error` ("not logged in") | Provider error with the billing message, not retried | Clients that map `provider.auth_error` to a re-login prompt for this case | None needed; the old message was wrong | | Chat-completions stream with DSML/Hermes tool tags | Tags shown as assistant text, no tool call | Parsed into tool calls. Text that could start a tag is held back until it is known not to be a tag, and `llm.streaming.finish` arrives after the stream ends | Nobody relies on raw tags | None | | `@pymodel/agent-core-v2/kosong/*` subpath import | Resolves | Gone | `apps/vis` (updated in this PR); no other consumer found in the repo | Import from `.../llm-adapter/*` | Affected modules and coverage: - `app/auth`: `test/app/auth/auth.test.ts` (provider-env key case, fails on `main`). - `app/kosongConfig/envOverlay`: new non-pythinker base-url case (fails on `main`). - `human/llm` openai format and stream: billing-error tests and DSML parser/recovery tests. - Full suites: agent-core-v2 6,491 pass, agent-gateway 1,407 pass, vis-server 173 pass; `tsc` and `tsgo` clean; no-comments and identity-freeze checks pass. ## Checklist - [x] I have read the [CONTRIBUTING](https://github.com/PyModel/pythinker-code/blob/main/CONTRIBUTING.md) document. - [x] I have linked a related issue (external PRs: issue must have a maintainer's `/approve`). - [x] I have added tests that prove my feature works. - [x] The behavior-change table above is complete, and every removed behavior or flipped default is named in the changeset and either has an escape hatch or was explicitly approved by a maintainer in this PR. - [x] Ran `gen-changesets` skill, or this PR needs no changeset. - [x] Ran `gen-docs` skill, or this PR needs no doc update.
1 parent 69cc714 commit 772e69a

98 files changed

Lines changed: 803 additions & 10897 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@pymodel/pythinker-code": patch
3+
"@pymodel/pythinker-desktop": patch
4+
---
5+
6+
Run tool calls that some models (such as DeepSeek) write as DSML or <tool_call> text instead of showing them as plain text.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@pymodel/pythinker-code": patch
3+
"@pymodel/pythinker-desktop": patch
4+
---
5+
6+
Report an insufficient-balance response from OpenAI-compatible providers as a billing error instead of an authentication error.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@pymodel/pythinker-code": patch
3+
"@pymodel/pythinker-desktop": patch
4+
---
5+
6+
Accept a vendor API key or base URL set in a provider's env table (for example ANTHROPIC_API_KEY or ANTHROPIC_BASE_URL) instead of ignoring it.

‎apps/pythinker-code/src/cli/sub/provider.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ export async function handleCatalogAdd(
406406

407407
// Always restore `[thinking]` from what was there before — including
408408
// `undefined`. Persisting `enabled: false` when the user never set it would
409-
// make `resolveThinkingEffort` (agent-core-v2/src/kosong/model/thinking.ts) treat
409+
// make `resolveThinkingEffort` (agent-core-v2/src/llm-adapter/model/thinking.ts) treat
410410
// it as an explicit "off" request and silently disable thinking, even for
411411
// thinking-capable models.
412412
config.thinking = previousThinking;

‎apps/vis/server/src/lib/context-projector.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
selectCompactionUserMessages,
1111
selectRecentUserMessages,
1212
} from '@pymodel/agent-core-v2/agent/contextMemory/compactionHandoff';
13-
import { estimateTokensForMessages } from '@pymodel/agent-core-v2/kosong/contract/tokens';
13+
import { estimateTokensForMessages } from '@pymodel/agent-core-v2/llm-adapter/contract/tokens';
1414
import { renderToolResultForModel } from '@pymodel/agent-core-v2/agent/contextMemory/toolResultRender';
1515
import type {
1616
ContentPart,
@@ -678,7 +678,7 @@ const MICRO_MIN_CONTENT_TOKENS = 100;
678678

679679
/** Replicates the engine's per-char token weighting exactly, over the same
680680
* `text` + `think` parts its gate counts. The engine
681-
* (`packages/agent-core-v2/src/kosong/contract/tokens.ts`) sums per-part
681+
* (`packages/agent-core-v2/src/llm-adapter/contract/tokens.ts`) sums per-part
682682
* estimates, each
683683
* `estimateTokens(s) = Math.ceil(asciiCount / 4) + nonAsciiCount` (ASCII ~4
684684
* chars/token, every non-ASCII/CJK code point a full token); other part types

‎apps/vis/server/test/lib/context-projector.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// apps/vis/server/test/lib/context-projector.test.ts
22
import { describe, it, expect, afterEach } from 'vitest';
3-
import { estimateTokensForMessages } from '@pymodel/agent-core-v2/kosong/contract/tokens';
3+
import { estimateTokensForMessages } from '@pymodel/agent-core-v2/llm-adapter/contract/tokens';
44
import { buildCompactionContinuationText } from '@pymodel/agent-core-v2/agent/contextMemory/compactionHandoff';
55
import { buildSessionFixture } from '../fixtures/build';
66
import { projectContext } from '../../src/lib/context-projector';

‎packages/agent-core-v2/src/app/auth/auth.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
22
import { Error2 } from '#/_base/errors/errors';
33

4-
import type { OAuthRef } from '#/kosong/provider/provider';
4+
import type { OAuthRef } from '#/llm-adapter/provider/provider';
55

66
import { AuthErrors } from './errors';
77

‎packages/agent-core-v2/src/app/auth/authService.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ import { type ILogger, ILogService } from '#/_base/log/log';
1414
import { IBootstrapService } from '#/app/bootstrap/bootstrap';
1515
import { IConfigService } from '#/app/config/config';
1616
import { LifecycleScope } from '#/app/scopes';
17-
import { IModelService, type ModelRecord } from '#/kosong/model/model';
17+
import { IModelService, type ModelRecord } from '#/llm-adapter/model/model';
1818
import {
1919
effectiveModelConfig,
2020
nonEmpty,
2121
providerNameFromFlatModel,
2222
resolveModelAuthMaterial,
2323
resolveModelForReady,
24-
} from '#/kosong/model/modelAuth';
25-
import { IProviderService, type OAuthRef } from '#/kosong/provider/provider';
24+
} from '#/llm-adapter/model/model-auth';
25+
import { IProviderService, type OAuthRef } from '#/llm-adapter/provider/provider';
2626

2727
import {
2828
AuthModelNotResolvedError,

‎packages/agent-core-v2/src/app/auth/authStatusService.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { ScopeActivation, registerScopedService } from '#/_base/di/scope';
22
import { LifecycleScope } from '#/app/scopes';
3-
import { IModelService } from '#/kosong/model/model';
4-
import { resolveModelForReady } from '#/kosong/model/modelAuth';
5-
import { IProviderService } from '#/kosong/provider/provider';
3+
import { IModelService } from '#/llm-adapter/model/model';
4+
import { resolveModelForReady } from '#/llm-adapter/model/model-auth';
5+
import { IProviderService } from '#/llm-adapter/provider/provider';
66

77
import type { AuthSummary } from './authStatus';
88
import { IAuthStatusService } from './authStatus';

‎packages/agent-core-v2/src/app/auth/configSection.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
transformPlainObject,
2020
} from '#/app/config/toml';
2121
import { type AssertExact, type Equal } from '#/_base/utils/typeEquality';
22-
import type { OAuthRef } from '#/kosong/provider/provider';
22+
import type { OAuthRef } from '#/llm-adapter/provider/provider';
2323

2424
export const SERVICES_SECTION = 'services';
2525

0 commit comments

Comments
 (0)