cartethyia 2.7.6 → 2.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +4 -2
- package/README.md +26 -2
- package/dist/dashboard/assets/{BannedPage-B1N603cM.js → BannedPage-DeIbIiBU.js} +1 -1
- package/dist/dashboard/assets/BannedPage-DeIbIiBU.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-9q4_4NoK.js → CliToolDetailPage-BRWKPQPf.js} +2 -2
- package/dist/dashboard/assets/CliToolDetailPage-BRWKPQPf.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-DwshGtK-.js → CliToolsPage-BP-Y5iHN.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-BP-Y5iHN.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-D762MIkp.js → CombosPage-B7JHozVa.js} +1 -1
- package/dist/dashboard/assets/CombosPage-B7JHozVa.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-KRYjHZsX.js → ConsoleLogPage-GX7iGGcu.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-GX7iGGcu.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-HVonbGEa.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-xaut4OJ9.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-d5v3K_Yc.js → InjectSystemPromptPanel-Cv-2Ol_K.js} +2 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-Cv-2Ol_K.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-Ccv4tjNS.js → LoginPage-bKfz_b1K.js} +1 -1
- package/dist/dashboard/assets/LoginPage-bKfz_b1K.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-C6WlpU5E.js → ModelPicker-B-WQxx_H.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-B-WQxx_H.js.gz +0 -0
- package/dist/dashboard/assets/{OverviewPage-DN9fYNWA.js → OverviewPage-Z9pJZFo8.js} +1 -1
- package/dist/dashboard/assets/OverviewPage-Z9pJZFo8.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js +7 -0
- package/dist/dashboard/assets/ProviderDetailPage-CMeDBxR1.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-BXPdOpG4.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-DxjvUzSD.js → ProxyPage-emlTuxz6.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-emlTuxz6.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js +1 -0
- package/dist/dashboard/assets/QuotaPage-CMJKNYWb.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-BW4AKj2H.js → SettingsPage-BSy7EwGo.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-BSy7EwGo.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-44BZrxFC.js → SetupPage-CUZrKs9r.js} +1 -1
- package/dist/dashboard/assets/SetupPage-CUZrKs9r.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-DRPavCdU.js → StudioPage-jWmUdDpy.js} +2 -2
- package/dist/dashboard/assets/StudioPage-jWmUdDpy.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-B7M7LvMM.js → UsagePage-5F9tGVGG.js} +2 -2
- package/dist/dashboard/assets/UsagePage-5F9tGVGG.js.gz +0 -0
- package/dist/dashboard/assets/app-BEsU8hKs.css +2 -0
- package/dist/dashboard/assets/app-BEsU8hKs.css.gz +0 -0
- package/dist/dashboard/assets/{badge-BaX2GTrn.js → badge-DyI_mJfi.js} +1 -1
- package/dist/dashboard/assets/badge-DyI_mJfi.js.gz +0 -0
- package/dist/dashboard/assets/{banner-DFH4Bkte.js → banner-B-yXtCqb.js} +1 -1
- package/dist/dashboard/assets/banner-B-yXtCqb.js.gz +0 -0
- package/dist/dashboard/assets/{card-CxDw0lK6.js → card-_HwAEqFF.js} +1 -1
- package/dist/dashboard/assets/card-_HwAEqFF.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-DfS1ntiq.js → clipboard-button-ByjCk0o9.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-ByjCk0o9.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-BK-NRr5u.js → dialog-BZhOlfz-.js} +1 -1
- package/dist/dashboard/assets/dialog-BZhOlfz-.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-QqPul6sC.js → drawer-BADqIRYP.js} +1 -1
- package/dist/dashboard/assets/drawer-BADqIRYP.js.gz +0 -0
- package/dist/dashboard/assets/{entry-BMlYJVFj.js → entry-COaC-irN.js} +1 -1
- package/dist/dashboard/assets/entry-COaC-irN.js.gz +0 -0
- package/dist/dashboard/assets/{entry-D6WvojeD.js → entry-Ca7LSCet.js} +3 -3
- package/dist/dashboard/assets/entry-Ca7LSCet.js.gz +0 -0
- package/dist/dashboard/assets/{entry-Dmx7gBiN.js → entry-PDd27uCd.js} +1 -1
- package/dist/dashboard/assets/entry-PDd27uCd.js.gz +0 -0
- package/dist/dashboard/assets/formatters-DcHJyRiu.js +1 -0
- package/dist/dashboard/assets/formatters-DcHJyRiu.js.gz +0 -0
- package/dist/dashboard/assets/{index-Ko4dLRnV.js → index-CgKmFngv.js} +2 -2
- package/dist/dashboard/assets/index-CgKmFngv.js.gz +0 -0
- package/dist/dashboard/assets/{inline-CHJQwxLa.js → inline-B2BirKry.js} +1 -1
- package/dist/dashboard/assets/inline-B2BirKry.js.gz +0 -0
- package/dist/dashboard/assets/{input-BlmlizVV.js → input-D-qV9dKY.js} +1 -1
- package/dist/dashboard/assets/input-D-qV9dKY.js.gz +0 -0
- package/dist/dashboard/assets/{layout-Bz93I-yE.js → layout-B7tVWhtV.js} +1 -1
- package/dist/dashboard/assets/layout-B7tVWhtV.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-Ml16KCfu.js → list-row-CtkGE3bF.js} +1 -1
- package/dist/dashboard/assets/list-row-CtkGE3bF.js.gz +0 -0
- package/dist/dashboard/assets/{live-7_QpnYGu.js → live-DGizF06-.js} +1 -1
- package/dist/dashboard/assets/live-DGizF06-.js.gz +0 -0
- package/dist/dashboard/assets/{network-CuMDaTzT.js → network-C9UE-bOr.js} +1 -1
- package/dist/dashboard/assets/network-C9UE-bOr.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-CoqpaDMx.js → numbers-BnAd22a_.js} +1 -1
- package/dist/dashboard/assets/numbers-BnAd22a_.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-B5aOoweZ.js → pagination-BAywyMzS.js} +1 -1
- package/dist/dashboard/assets/pagination-BAywyMzS.js.gz +0 -0
- package/dist/dashboard/assets/{select-BHqpd-bg.js → select-BCOcv8WK.js} +1 -1
- package/dist/dashboard/assets/select-BCOcv8WK.js.gz +0 -0
- package/dist/dashboard/assets/{stack-j7cXjgYX.js → stack-ByKt3Q1g.js} +1 -1
- package/dist/dashboard/assets/stack-ByKt3Q1g.js.gz +0 -0
- package/dist/dashboard/assets/{state-DHmEnLa2.js → state-DXbGTx7N.js} +1 -1
- package/dist/dashboard/assets/state-DXbGTx7N.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DmZcyTIC.js → switch-ZeTsW623.js} +1 -1
- package/dist/dashboard/assets/switch-ZeTsW623.js.gz +0 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +44 -44
- package/migrations/full-postgres/0043_add_provider_custom_user_agent.sql +14 -0
- package/migrations/lite-sqlite3/0043_add_provider_custom_user_agent.sql +10 -0
- package/package.json +3 -1
- package/src/config/security.ts +1 -1
- package/src/config/spec.ts +13 -6
- package/src/console/cli-tools/cartethyia-websearch-plugin.ts +186 -0
- package/src/console/cli-tools/injectors/opencode.ts +42 -14
- package/src/console/providers/catalog/contracts.ts +4 -0
- package/src/console/providers/catalog/routes.ts +1 -0
- package/src/console/providers/detail/routes.ts +8 -0
- package/src/console/providers/detail/store.ts +4 -0
- package/src/console/quota/account-quota-tenant.ts +74 -0
- package/src/observability/telemetry-buffer.ts +2 -1
- package/src/persistence/boot.ts +13 -4
- package/src/persistence/schema.ts +7 -1
- package/src/protocol/canonical-model.ts +7 -0
- package/src/protocol/request/gemini.ts +20 -1
- package/src/protocol/tool-identity.ts +15 -1
- package/src/providers/default-registry.ts +8 -2
- package/src/providers/discovery/discovery-types.ts +18 -0
- package/src/providers/discovery/probe-phases.ts +45 -0
- package/src/providers/discovery/probing-service.ts +41 -6
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +20 -2
- package/src/providers/integrations/antigravity/antigravity.ts +24 -0
- package/src/providers/integrations/codex/codex.ts +28 -1
- package/src/providers/integrations/qoder/chat-proto.ts +328 -0
- package/src/providers/integrations/qoder/claim-qoder-credits.ts +258 -0
- package/src/providers/integrations/qoder/qoder-adapter.ts +275 -0
- package/src/providers/integrations/qoder/qoder-auth.ts +201 -0
- package/src/providers/integrations/qoder/qoder-body.ts +78 -0
- package/src/providers/integrations/qoder/qoder-catalog.ts +5 -1
- package/src/providers/integrations/qoder/qoder-chat.ts +514 -0
- package/src/providers/integrations/qoder/qoder-cosy.ts +162 -0
- package/src/providers/integrations/qoder/qoder-grpc.ts +667 -0
- package/src/providers/integrations/qoder/qoder-ide.ts +584 -0
- package/src/providers/integrations/qoder/qoder-oauth.ts +4 -4
- package/src/providers/integrations/qoder/qoder-quota.ts +1 -1
- package/src/providers/integrations/qoder/qoder-realm.ts +40 -4
- package/src/providers/integrations/qoder/qoder-types.ts +51 -0
- package/src/providers/integrations/qoder/qoder-umid.ts +123 -8
- package/src/providers/integrations/qoder/qoder.ts +147 -1889
- package/src/providers/operations/byok-wire-profile.ts +9 -12
- package/src/providers/provider-contracts.ts +7 -0
- package/src/providers/provider-usage.ts +9 -6
- package/src/security/abuse.ts +5 -5
- package/src/transport/dispatch/upstream.ts +4 -2
- package/src/transport/routing/route-catalog.ts +8 -0
- package/src/transport/routing/route-model.ts +4 -0
- package/src/transport/routing/router.ts +8 -117
- package/dist/dashboard/assets/BannedPage-B1N603cM.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-9q4_4NoK.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-DwshGtK-.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-D762MIkp.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-KRYjHZsX.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-DXsxk5uM.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-DXsxk5uM.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BEjAjD4F.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-BEjAjD4F.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-d5v3K_Yc.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-Ccv4tjNS.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-C6WlpU5E.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-DN9fYNWA.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-BY0ieUgz.js +0 -5
- package/dist/dashboard/assets/ProviderDetailPage-BY0ieUgz.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DNsbfea2.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-DNsbfea2.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-DxjvUzSD.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-OaHScjpX.js +0 -1
- package/dist/dashboard/assets/QuotaPage-OaHScjpX.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BW4AKj2H.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-44BZrxFC.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-DRPavCdU.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-B7M7LvMM.js.gz +0 -0
- package/dist/dashboard/assets/app-mweRcsKp.css +0 -2
- package/dist/dashboard/assets/app-mweRcsKp.css.gz +0 -0
- package/dist/dashboard/assets/badge-BaX2GTrn.js.gz +0 -0
- package/dist/dashboard/assets/banner-DFH4Bkte.js.gz +0 -0
- package/dist/dashboard/assets/card-CxDw0lK6.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DfS1ntiq.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BK-NRr5u.js.gz +0 -0
- package/dist/dashboard/assets/drawer-QqPul6sC.js.gz +0 -0
- package/dist/dashboard/assets/entry-BMlYJVFj.js.gz +0 -0
- package/dist/dashboard/assets/entry-D6WvojeD.js.gz +0 -0
- package/dist/dashboard/assets/entry-Dmx7gBiN.js.gz +0 -0
- package/dist/dashboard/assets/formatters-u7JPB5BX.js +0 -1
- package/dist/dashboard/assets/formatters-u7JPB5BX.js.gz +0 -0
- package/dist/dashboard/assets/index-Ko4dLRnV.js.gz +0 -0
- package/dist/dashboard/assets/inline-CHJQwxLa.js.gz +0 -0
- package/dist/dashboard/assets/input-BlmlizVV.js.gz +0 -0
- package/dist/dashboard/assets/layout-Bz93I-yE.js.gz +0 -0
- package/dist/dashboard/assets/list-row-Ml16KCfu.js.gz +0 -0
- package/dist/dashboard/assets/live-7_QpnYGu.js.gz +0 -0
- package/dist/dashboard/assets/network-CuMDaTzT.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CoqpaDMx.js.gz +0 -0
- package/dist/dashboard/assets/pagination-B5aOoweZ.js.gz +0 -0
- package/dist/dashboard/assets/select-BHqpd-bg.js.gz +0 -0
- package/dist/dashboard/assets/stack-j7cXjgYX.js.gz +0 -0
- package/dist/dashboard/assets/state-DHmEnLa2.js.gz +0 -0
- package/dist/dashboard/assets/switch-DmZcyTIC.js.gz +0 -0
- package/src/providers/integrations/qoder/qoder-text-tools.ts +0 -277
- /package/dist/dashboard/assets/{app-DRnQLhsc.js → app-CTxoqyYB.js} +0 -0
- /package/dist/dashboard/assets/{app-DRnQLhsc.js.gz → app-CTxoqyYB.js.gz} +0 -0
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
// Sourced from the donor hub `Public/qoder2api-hub` (`REALM_CONFIGS` in
|
|
14
14
|
// `qoder_accounts.py`), which builds every upstream URL from this table.
|
|
15
15
|
|
|
16
|
+
/** The bundled provider id for the intl deployment. */
|
|
17
|
+
export const QODER_PROVIDER_ID = "qoder" as const;
|
|
18
|
+
|
|
16
19
|
/** One Qoder deployment. */
|
|
17
20
|
export type QoderRealm = "intl" | "cn";
|
|
18
21
|
|
|
@@ -30,10 +33,30 @@ export interface QoderRealmConfig {
|
|
|
30
33
|
readonly clientId: string;
|
|
31
34
|
/** Native redirect the desktop client registers. */
|
|
32
35
|
readonly redirectUri: string;
|
|
33
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Upstream-facing User-Agent for the openapi/business surface. Verified
|
|
38
|
+
* live: the /sash campaign surface accepts plain `Qoder` / `QoderWork`
|
|
39
|
+
* (no version token).
|
|
40
|
+
*/
|
|
34
41
|
readonly userAgent: string;
|
|
42
|
+
/**
|
|
43
|
+
* Job-token exchange endpoint for PAT → jt (v1, plain JSON body).
|
|
44
|
+
* Verified live: `POST center.qoder.sh/api/v1/jobToken/exchange` with
|
|
45
|
+
* `{personal_token, machine_id}` returns `{token: jt-…, …}`.
|
|
46
|
+
*/
|
|
47
|
+
readonly jobTokenUrl: string;
|
|
35
48
|
}
|
|
36
49
|
|
|
50
|
+
/**
|
|
51
|
+
* User agents observed in actual live captures of the real client
|
|
52
|
+
* (2026-11-05, mitmproxy):
|
|
53
|
+
* - CLI / legacy COSY chat + device flow: `Bun/1.4.2` (flow 218/030, v1.1.83)
|
|
54
|
+
* - IDE surface `/model/v1`: `Bun/1.4.4` (flow 219)
|
|
55
|
+
* - job-token exchange: `qoder/<version>` (capture, e.g. `qoder/1.1.83`)
|
|
56
|
+
*/
|
|
57
|
+
export const QODER_UA_CLI = "Bun/1.4.2";
|
|
58
|
+
export const QODER_UA_IDE = "Bun/1.4.4";
|
|
59
|
+
|
|
37
60
|
/** Chat + model-list paths, identical across realms. */
|
|
38
61
|
export const QODER_CHAT_PATH =
|
|
39
62
|
"/algo/api/v2/service/pro/sse/agent_chat_generation?FetchKeys=llm_model_result&AgentId=agent_common&Encode=1";
|
|
@@ -58,6 +81,17 @@ export function qoderIdeChatUrl(realm: QoderRealm): string {
|
|
|
58
81
|
: `https://api2-v2.qoder.sh${QODER_IDE_CHAT_PATH}`;
|
|
59
82
|
}
|
|
60
83
|
|
|
84
|
+
/**
|
|
85
|
+
* The gRPC model-server host for one realm.
|
|
86
|
+
*
|
|
87
|
+
* Matches the desktop client's prod host (`wYA` in `qoder-worker-runtime.mjs`:
|
|
88
|
+
* intl `api2-v2.qoder.sh`, CN `gateway.qoder.com.cn`). The gRPC service path
|
|
89
|
+
* is `/model.chat.ChatService/ChatCompletionStream` on port 443.
|
|
90
|
+
*/
|
|
91
|
+
export function qoderGrpcHost(realm: QoderRealm): string {
|
|
92
|
+
return realm === "cn" ? "gateway.qoder.com.cn" : "api2-v2.qoder.sh";
|
|
93
|
+
}
|
|
94
|
+
|
|
61
95
|
/**
|
|
62
96
|
* Realm → deployment.
|
|
63
97
|
*
|
|
@@ -73,7 +107,8 @@ export const QODER_REALMS: Readonly<Record<QoderRealm, QoderRealmConfig>> = {
|
|
|
73
107
|
website: "https://qoder.com",
|
|
74
108
|
clientId: "e883ade2-e6e3-4d6d-adf7-f92ceff5fdcb",
|
|
75
109
|
redirectUri: "qoder://aicoding.aicoding-agent/login-success",
|
|
76
|
-
userAgent: "Qoder
|
|
110
|
+
userAgent: "Qoder",
|
|
111
|
+
jobTokenUrl: "https://center.qoder.sh/api/v1/jobToken/exchange",
|
|
77
112
|
},
|
|
78
113
|
cn: {
|
|
79
114
|
gateway: "https://gateway.qoder.com.cn",
|
|
@@ -84,7 +119,8 @@ export const QODER_REALMS: Readonly<Record<QoderRealm, QoderRealmConfig>> = {
|
|
|
84
119
|
// the client id the IDE registers for the CN deployment.
|
|
85
120
|
clientId: "e93fe488-5778-4c35-a6fc-0f54ed7b3139",
|
|
86
121
|
redirectUri: "qoder-work-cn://",
|
|
87
|
-
userAgent: "QoderWork
|
|
122
|
+
userAgent: "QoderWork",
|
|
123
|
+
jobTokenUrl: "https://openapi.qoder.com.cn/api/v1/jobToken/exchange",
|
|
88
124
|
},
|
|
89
125
|
};
|
|
90
126
|
|
|
@@ -101,7 +137,7 @@ export function qoderGatewayCandidates(realm: QoderRealm): readonly string[] {
|
|
|
101
137
|
|
|
102
138
|
/**
|
|
103
139
|
* Inference hosts for one realm, ordered by token kind — mirrors the donor's
|
|
104
|
-
* `qoderInferenceBase` (
|
|
140
|
+
* `qoderInferenceBase` (donor hub): job tokens (`jt-...`) are served by `api2`
|
|
105
141
|
* and get rejected by `api3` with "Login expired" (403), while device tokens
|
|
106
142
|
* (`dt-...`) and everything else use the ordinary gateway set. CN has a
|
|
107
143
|
* single gateway and serves every token kind.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Qoder types.
|
|
3
|
+
*
|
|
4
|
+
* Kept as a separate module so the transports (gRPC, IDE/HTTP, legacy COSY)
|
|
5
|
+
* and the auth layer share one shape without importing each other.
|
|
6
|
+
*/
|
|
7
|
+
import type { QoderRealm } from "./qoder-realm";
|
|
8
|
+
|
|
9
|
+
export type { QoderRealm };
|
|
10
|
+
|
|
11
|
+
export interface QoderAuth {
|
|
12
|
+
userId: string;
|
|
13
|
+
userName: string;
|
|
14
|
+
userType: string;
|
|
15
|
+
securityOauthToken: string;
|
|
16
|
+
refreshToken: string;
|
|
17
|
+
/** One UUID-like machine id used in COSY signing and session identity. */
|
|
18
|
+
machineId: string;
|
|
19
|
+
/**
|
|
20
|
+
* Native UMID machine token (`P1gA…`), when a native identity is bound.
|
|
21
|
+
* Absent for OAuth device tokens; the COSY header set then falls back to
|
|
22
|
+
* `machineId` for the token/type/code fields.
|
|
23
|
+
*/
|
|
24
|
+
machineToken?: string;
|
|
25
|
+
/** Native UMID machine type (18-hex), when known. */
|
|
26
|
+
machineType?: string;
|
|
27
|
+
/** Native UMID machine code (18-hex), when known. */
|
|
28
|
+
machineCode?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface QoderModelConfig {
|
|
32
|
+
id: string;
|
|
33
|
+
display_name: string;
|
|
34
|
+
max_input_tokens: number;
|
|
35
|
+
max_output_tokens?: number;
|
|
36
|
+
is_vl: boolean;
|
|
37
|
+
is_reasoning: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Whether upstream publishes this row as free. Only an explicit `true`
|
|
40
|
+
* marks the model as free-tier; absent or `false` means billed/unknown, so
|
|
41
|
+
* a 402/116 there is genuine quota exhaustion, not a free-tier entitlement
|
|
42
|
+
* refusal, and must not trigger a legacy-surface fallback.
|
|
43
|
+
*/
|
|
44
|
+
is_free?: boolean;
|
|
45
|
+
source?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Reasoning effort to send when the caller did not ask for one. Some models
|
|
48
|
+
* on the IDE surface stream no reasoning unless an effort is present.
|
|
49
|
+
*/
|
|
50
|
+
default_reasoning_effort?: string;
|
|
51
|
+
}
|
|
@@ -34,8 +34,10 @@
|
|
|
34
34
|
import { spawnSync } from "node:child_process";
|
|
35
35
|
import { createHash } from "node:crypto";
|
|
36
36
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
37
|
+
import { tmpdir } from "node:os";
|
|
37
38
|
import { join } from "node:path";
|
|
38
39
|
import { log } from "../../../observability/logger";
|
|
40
|
+
import { qoderMachineHostname, qoderMachineOs } from "../../operations/cli-platform";
|
|
39
41
|
|
|
40
42
|
/** Environment code: an upstream "region/flavor", not a device identity. */
|
|
41
43
|
const ENV_CODE = "3";
|
|
@@ -60,12 +62,66 @@ export function umidpackDir(): string {
|
|
|
60
62
|
return join(process.cwd(), "vendor", "qoder-umid", "umidpack");
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
/** The vendored mini-ELF tarball (static glibc bundle for containers). */
|
|
66
|
+
export function umidMiniTarball(): string {
|
|
67
|
+
return join(process.cwd(), "vendor", "qoder-umid", "qoder-umid-mini.tar.gz");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Staging directory for the extracted mini-ELF bundle. Uses a per-user cache
|
|
72
|
+
* dir (respecting `XDG_CACHE_HOME` on Linux) so a container does not need to
|
|
73
|
+
* carry the extracted tree, and the repo stays clean.
|
|
74
|
+
*/
|
|
75
|
+
export function umidMiniDir(): string {
|
|
76
|
+
const base = process.env.XDG_CACHE_HOME?.trim() || join(process.env.HOME ?? tmpdir(), ".cache");
|
|
77
|
+
return join(base, "cartethyia", "umid-mini");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let miniExtracted = false;
|
|
81
|
+
|
|
82
|
+
/** Extracts `qoder-umid-mini.tar.gz` once into {@link umidMiniDir}. */
|
|
83
|
+
export function ensureUmidMiniExtracted(): boolean {
|
|
84
|
+
const tar = umidMiniTarball();
|
|
85
|
+
const out = umidMiniDir();
|
|
86
|
+
if (!existsSync(tar)) return false;
|
|
87
|
+
if (existsSync(join(out, "opt", "umid", "runtime-info")) && miniExtracted) return true;
|
|
88
|
+
try {
|
|
89
|
+
mkdirSync(out, { recursive: true });
|
|
90
|
+
// Re-extract on empty/missing target only; leave existing tree in place.
|
|
91
|
+
if (!existsSync(join(out, "opt", "umid", "runtime-info"))) {
|
|
92
|
+
const r = spawnSync("tar", ["-xzf", tar, "-C", out], { encoding: "utf8", timeout: 30_000 });
|
|
93
|
+
if (r.status !== 0) {
|
|
94
|
+
log.warn(`[qoder] umid mini extract failed: ${String(r.stderr ?? "").slice(0, 300)}`);
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const bin = join(out, "opt", "umid", "runtime-info");
|
|
99
|
+
if (existsSync(bin)) chmodSync(bin, 0o755);
|
|
100
|
+
miniExtracted = true;
|
|
101
|
+
return true;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
log.warn("[qoder] umid mini extract error", error as Error);
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
63
108
|
/** WSL-side staging/seed paths for the Linux bridge on a Windows host. */
|
|
64
109
|
const WSL_BIN = "/tmp/umidpack/runtime-info";
|
|
65
110
|
const WSL_SEED = "/home/risun/.config/.locale_cfg";
|
|
66
111
|
const WSL_LOCK = "/tmp/umidpack/bridge.lock";
|
|
67
112
|
|
|
68
|
-
type Bridge = { kind: "elf" | "macho" | "pe" | "wsl-elf" };
|
|
113
|
+
type Bridge = { kind: "elf" | "macho" | "pe" | "wsl-elf" | "mini-elf" };
|
|
114
|
+
|
|
115
|
+
/** True when this process runs inside a container (Docker/Podman). */
|
|
116
|
+
function isContainerized(): boolean {
|
|
117
|
+
if (existsSync(join("/", ".dockerenv")) || existsSync(join("/", ".containerenv"))) return true;
|
|
118
|
+
try {
|
|
119
|
+
const cgroup = readFileSync("/proc/1/cgroup", "utf8");
|
|
120
|
+
return /(?:docker|containerd|podman|kubepods)/i.test(cgroup);
|
|
121
|
+
} catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
69
125
|
|
|
70
126
|
/** Picks the bridge for this host; `null` when nothing genuine can run. */
|
|
71
127
|
function pickBridge(): Bridge | null {
|
|
@@ -77,15 +133,34 @@ function pickBridge(): Bridge | null {
|
|
|
77
133
|
const exe = join(umidpackDir(), "runtime-info.exe");
|
|
78
134
|
const dll = join(umidpackDir(), "sgsdk.dll");
|
|
79
135
|
if (existsSync(exe) && existsSync(dll)) return { kind: "pe" };
|
|
80
|
-
|
|
136
|
+
// Windows has no native mini-ELF runner; WSL may still be present.
|
|
137
|
+
return ensureUmidMiniExtracted() ? { kind: "wsl-elf" } : null;
|
|
81
138
|
}
|
|
82
139
|
if (process.platform === "darwin") {
|
|
83
140
|
const arch = process.arch === "arm64" ? "macho-arm64" : "macho-x86_64";
|
|
141
|
+
// Prefer the re-signed adhoc `.clean` binary (DYLD-spoofable); the stock
|
|
142
|
+
// vendored binary is Hardened Runtime and ignores DYLD_INSERT_LIBRARIES.
|
|
143
|
+
const clean = join(umidpackDir(), arch, "runtime-info.clean");
|
|
144
|
+
if (existsSync(clean)) return { kind: "macho" };
|
|
84
145
|
return existsSync(join(umidpackDir(), arch, "runtime-info")) ? { kind: "macho" } : null;
|
|
85
146
|
}
|
|
147
|
+
// Linux. Containers (Docker/K8s/Podman) prefer the mini-ELF bundle: it ships
|
|
148
|
+
// a static glibc tree and mints tokens with `isVm:false`, so a containerized
|
|
149
|
+
// deployment is not flagged as a VM. Set CARTETHYIA_QODER_UMID_MINI=1 to
|
|
150
|
+
// force it on any Linux host, or =0 to disable container detection.
|
|
151
|
+
const miniForced = process.env.CARTETHYIA_QODER_UMID_MINI;
|
|
152
|
+
const wantMini =
|
|
153
|
+
miniForced === "1" || (miniForced !== "0" && isContainerized());
|
|
154
|
+
if (wantMini && process.arch === "x64" && ensureUmidMiniExtracted()) {
|
|
155
|
+
return { kind: "mini-elf" };
|
|
156
|
+
}
|
|
86
157
|
const arch = process.arch === "arm64" ? "elf-aarch64" : "elf-x86_64";
|
|
87
158
|
if (existsSync(join(umidpackDir(), arch, "runtime-info"))) return { kind: "elf" };
|
|
88
|
-
|
|
159
|
+
if (existsSync(join(umidpackDir(), "elf-x86_64", "runtime-info"))) return { kind: "elf" };
|
|
160
|
+
// Linux without a native vendored ELF (e.g. a slim container with no mini):
|
|
161
|
+
// fall back to the mini-ELF bundle when it extracts.
|
|
162
|
+
if (ensureUmidMiniExtracted() && process.arch === "x64") return { kind: "mini-elf" };
|
|
163
|
+
return null;
|
|
89
164
|
}
|
|
90
165
|
|
|
91
166
|
/** Generated (software) identity — deterministic per account id, NOT SG-signed. */
|
|
@@ -216,6 +291,23 @@ export function mintQoderNativeIdentity(accountId: string): { identity: QoderNat
|
|
|
216
291
|
return { identity, seed: existsSync(seed) ? readFileSync(seed, "utf8") : "" };
|
|
217
292
|
}
|
|
218
293
|
|
|
294
|
+
if (bridge.kind === "mini-elf") {
|
|
295
|
+
const exe = join(umidMiniDir(), "opt", "umid", "runtime-info");
|
|
296
|
+
ensurePosixExecutable(exe);
|
|
297
|
+
const seed = seedPath();
|
|
298
|
+
const dir = seed.slice(0, seed.lastIndexOf("/"));
|
|
299
|
+
mkdirSync(dir, { recursive: true });
|
|
300
|
+
try { spawnSync("rm", ["-f", seed]); } catch { /* ignore */ }
|
|
301
|
+
const r = spawnSync(exe, [ENV_CODE], {
|
|
302
|
+
encoding: "utf8",
|
|
303
|
+
timeout: 25_000,
|
|
304
|
+
env: { ...process.env, LD_LIBRARY_PATH: join(umidMiniDir(), "lib", "x86_64-linux-gnu") },
|
|
305
|
+
});
|
|
306
|
+
const identity = parseBridgeLine(String(r.stdout ?? ""));
|
|
307
|
+
if (!identity) { log.warn("[qoder] mini-elf mint failed"); return null; }
|
|
308
|
+
return { identity, seed: existsSync(seed) ? readFileSync(seed, "utf8") : "" };
|
|
309
|
+
}
|
|
310
|
+
|
|
219
311
|
// pe — hardware-bound; no seed concept on Windows.
|
|
220
312
|
const exe = join(umidpackDir(), "runtime-info.exe");
|
|
221
313
|
const r = spawnSync(exe, [ENV_CODE], { encoding: "utf8", timeout: 30_000, windowsHide: true, cwd: umidpackDir() });
|
|
@@ -267,17 +359,40 @@ export function replayQoderNativeIdentity(seed: string, accountId: string): Qode
|
|
|
267
359
|
return parseBridgeLine(String(r.stdout ?? ""));
|
|
268
360
|
}
|
|
269
361
|
|
|
362
|
+
if (bridge.kind === "mini-elf") {
|
|
363
|
+
const exe = join(umidMiniDir(), "opt", "umid", "runtime-info");
|
|
364
|
+
ensurePosixExecutable(exe);
|
|
365
|
+
const seedPathStr = seedPath();
|
|
366
|
+
writeFileSync(seedPathStr, seed, "utf8");
|
|
367
|
+
const r = spawnSync(exe, [ENV_CODE], {
|
|
368
|
+
encoding: "utf8",
|
|
369
|
+
timeout: 25_000,
|
|
370
|
+
env: { ...process.env, LD_LIBRARY_PATH: join(umidMiniDir(), "lib", "x86_64-linux-gnu") },
|
|
371
|
+
});
|
|
372
|
+
return parseBridgeLine(String(r.stdout ?? ""));
|
|
373
|
+
}
|
|
374
|
+
|
|
270
375
|
return null; // pe: cannot replay a seed on a hardware-bound identity
|
|
271
376
|
}
|
|
272
377
|
|
|
273
|
-
/**
|
|
274
|
-
|
|
378
|
+
/**
|
|
379
|
+
* The six native machine headers for one identity.
|
|
380
|
+
*
|
|
381
|
+
* The upstream treats `cosy-machineid` as a stable UUID-shaped machine id
|
|
382
|
+
* (the desktop client caches it; the donor hub derives a UUIDv4 from the
|
|
383
|
+
* account uid). The native machineToken/Type/Code come from the UMID bridge.
|
|
384
|
+
* The OS and hostname are derived from the real runtime (`qoderMachineOs` /
|
|
385
|
+
* `qoderMachineHostname`), not hardcoded — the campaign surface fingerprint
|
|
386
|
+
* must match the actual client (verified live 2026-11-04).
|
|
387
|
+
*/
|
|
388
|
+
export function qoderMachineHeaders(identity: QoderNativeIdentity, machineId?: string): Record<string, string> {
|
|
389
|
+
const mid = machineId && machineId.length > 0 ? machineId : identity.machineToken;
|
|
275
390
|
return {
|
|
276
|
-
"cosy-machineid":
|
|
391
|
+
"cosy-machineid": mid,
|
|
277
392
|
"cosy-machinetoken": identity.machineToken,
|
|
278
393
|
"cosy-machinetype": identity.machineType,
|
|
279
394
|
"cosy-machinecode": identity.machineCode,
|
|
280
|
-
"cosy-machineos":
|
|
281
|
-
"cosy-machinehostname":
|
|
395
|
+
"cosy-machineos": qoderMachineOs(),
|
|
396
|
+
"cosy-machinehostname": qoderMachineHostname(),
|
|
282
397
|
};
|
|
283
398
|
}
|