Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions scripts/runtime-start-plan-patches.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { parseExpressionAt, type FunctionExpression } from "acorn";

const identifier = "[A-Za-z_$][\\w$]*";

function incompatible(detail: string): never {
throw new Error(`ZCode runtime is incompatible with standalone Start Plan support (${detail}).`);
}

function unique<Item>(items: Item[], name: string): Item {
if (items.length !== 1) incompatible(`${name} missing or ambiguous`);
return items[0]!;
}

function escapePattern(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
}

function namedFunction(runtime: string, label: string): FunctionExpression {
const name = unique([...runtime.matchAll(new RegExp(`${identifier}\\((${identifier}),"${label}"\\)`, "gu"))], label)[1]!;
const start = unique([...runtime.matchAll(new RegExp(`(?:async )?function ${escapePattern(name)}\\(`, "gu"))], label).index!;
const fn = parseExpressionAt(runtime, start, { ecmaVersion: "latest" });
if (fn.type !== "FunctionExpression") incompatible(`${label} declaration changed`);
return fn;
}

/**
* Admit z.ai/bigmodel Start Plan providers (for example the ZCODE WEEKEND BUILD
* token promos) in standalone mode. The bundled provider catalog already ships
* `account:zai-start-plan` / `account:bigmodel-start-plan`, and Desktop's host
* registers them whenever the account is entitled, but the standalone account
* paths only enumerate Individual Coding Plan providers. Five minimal edits:
*
* 1. the standalone catalog reader enumerates Start Plan providers as well, so
* they reach the provider registry and get an entitlement computed;
* 2. the login provider resolution keeps its "exactly one per family" contract
* by excluding Start Plan providers (login must keep targeting the Coding
* Plan api-key flow);
* 3. the standalone identity credential key maps Start Plan providers to the
* family's Coding Plan identity — the identity is account-scoped, and the
* server reports Start Plan grants for the same account;
* 4. the request credential refresher accepts the Start Plan access mode;
* 5. Start Plan requests authenticate with the zcode-scoped JWT credential
* (`zcodejwttoken`): Start Plan traffic is served from
* `zcode.z.ai/api/v1/zcode-plan/...`, which rejects the Coding Plan api-key
* and the exchanged api.z.ai project JWT with 401.
*
* No protocol changes: requests are still built and signed by the genuine
* runtime; only provider enumeration and credential selection are widened to
* cover an entitlement the account already has.
*/

/** True when the runtime already admits Start Plan providers standalone (native or patched). */
export function hasRuntimeStartPlan(runtime: string): boolean {
try {
const catalog = namedFunction(runtime, "readStandaloneCodingPlanCatalog");
const headers = namedFunction(runtime, "createStandaloneProviderRuntimeHeadersPort");
return runtime.slice(catalog.start, catalog.end).includes('"start-plan"')
&& runtime.slice(headers.start, headers.end).includes('mode==="start-plan"');
} catch {
return false;
}
}

export function patchRuntimeStartPlan(runtime: string): string {
if (hasRuntimeStartPlan(runtime)) return runtime;
const edits: { start: number; end: number; value: string }[] = [];
const plan = (label: string, pattern: RegExp, value: string, name: string) => {
const fn = namedFunction(runtime, label);
const source = runtime.slice(fn.start, fn.end);
const match = unique([...source.matchAll(pattern)], name);
edits.push({ start: fn.start + match.index!, end: fn.start + match.index! + match[0].length, value });
};

// 1) standalone catalog: enumerate Start Plan providers beside Coding Plan providers
plan("readStandaloneCodingPlanCatalog",
new RegExp('c\\?\\.type==="zhipu-account"&&c\\.mode==="individual-coding-plan"&&c\\.accountType&&u', "gu"),
'c?.type==="zhipu-account"&&(c.mode==="individual-coding-plan"||c.mode==="start-plan")&&c.accountType&&u',
"catalog provider predicate");

// 2) login: keep resolving the Coding Plan provider uniquely (Start Plan must not break it)
plan("resolveStandaloneCodingPlanProvider",
new RegExp('\\.filter\\(o=>o\\.family===e\\);if\\(n\\.length!==1\\)', "gu"),
'.filter(o=>o.family===e&&!/-start-plan$/.test(o.providerId));if(n.length!==1)',
"login provider uniqueness filter");

// 3) identity: Start Plan providers share the family's account-scoped identity credential
plan("standaloneAccountIdentityCredentialKey",
new RegExp('return`account-provider:\\$\\{t\\}:identity`', "gu"),
'return`account-provider:${/-start-plan$/.test(t)?t.replace(/-start-plan$/u,"-individual-coding-plan"):t}:identity`',
"identity credential key mapping");

// 4) request refresher: accept the Start Plan access mode
plan("createStandaloneProviderRuntimeHeadersPort",
new RegExp('if\\(!f\\|\\|f\\.mode!=="individual-coding-plan"\\)throw', "gu"),
'if(!f||f.mode!=="individual-coding-plan"&&f.mode!=="start-plan")throw',
"request access mode check");

// 5) request auth: Start Plan requests use the zcode-scoped JWT credential
const credentialCheck = 'if(c.abortSignal?.throwIfAborted(),!R)throw new Error(`Standalone Account Provider \\u7F3A\\u5C11\\u8BF7\\u6C42\\u51ED\\u636E: ${u}`)';
plan("createStandaloneProviderRuntimeHeadersPort",
new RegExp(escapePattern(credentialCheck), "gu"),
`if(f?.mode==="start-plan"){let $zSpToken=(await e.load("zcodejwttoken"))?.trim();if($zSpToken)R=$zSpToken}${credentialCheck}`,
"Start Plan request auth override");

edits.sort((first, second) => second.start - first.start);
let output = runtime;
for (const edit of edits) output = output.slice(0, edit.start) + edit.value + output.slice(edit.end);
return output;
}
6 changes: 6 additions & 0 deletions scripts/sync-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export { hasRuntimeSessionLifecycle, patchRuntimeSessionLifecycle } from "./runt
import { hasRuntimeBigmodelOAuth, patchRuntimeBigmodelOAuth } from "./runtime-bigmodel-oauth-patches.ts";
import { hasRuntimeSharedOAuth, patchRuntimeSharedOAuth } from "./runtime-shared-oauth-patches.ts";
export { hasRuntimeSharedOAuth, patchRuntimeSharedOAuth } from "./runtime-shared-oauth-patches.ts";
import { hasRuntimeStartPlan, patchRuntimeStartPlan } from "./runtime-start-plan-patches.ts";
export { hasRuntimeStartPlan, patchRuntimeStartPlan } from "./runtime-start-plan-patches.ts";
export { hasRuntimeBigmodelOAuth, patchRuntimeBigmodelOAuth } from "./runtime-bigmodel-oauth-patches.ts";
import { hasRuntimeBrowserUrlOpening, patchRuntimeBrowserUrlOpening } from "./runtime-browser-patches.ts";
export { hasRuntimeBrowserUrlOpening, patchRuntimeBrowserUrlOpening } from "./runtime-browser-patches.ts";
Expand Down Expand Up @@ -1967,6 +1969,10 @@ export const runtimePatchPlan: readonly RuntimePatchDefinition[] = [
id: "shared-oauth", requirement: "required", apply: patchRuntimeSharedOAuth,
verify: hasRuntimeSharedOAuth
},
{
id: "start-plan-standalone", requirement: "optional", apply: patchRuntimeStartPlan,
verify: hasRuntimeStartPlan
},
{
id: "login-model-defaults",
requirement: "required",
Expand Down
55 changes: 55 additions & 0 deletions test/runtime/start-plan.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { expect, test } from "bun:test";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import { hasRuntimeStartPlan, patchRuntimeStartPlan } from "../../scripts/runtime-start-plan-patches.ts";

const root = join(import.meta.dir, "../..");

// Minimal stand-in for the vendored runtime: the four account functions the
// Start Plan patches touch, in their real (pristine, minified) statement
// shapes. Escapes are doubled where the outer template would consume them, so
// the fixture string carries the same literal \uXXXX / ${...} text as bundle.
const fixture = `
var K3n=require("node:crypto");
async function Y3n(e,t){if(t)return{zcodeBuiltinRevision:t.revision,providers:t.providers.entries().flatMap(([i,a])=>{let c=a.access,u=a.builtinModelIds?.find(f=>f.trim())?.trim();return c?.type==="zhipu-account"&&c.mode==="individual-coding-plan"&&c.accountType&&u?[{family:c.accountType,modelId:u,providerId:i}]:[]})};throw new Error(\`\${VG} is required for login\`)}
r(Y3n,"readStandaloneCodingPlanCatalog");
async function Kgi(e,t){let n=(await Z3n(t)).filter(o=>o.family===e);if(n.length!==1)throw new Error(\`ZCode Built-in Config \\u5FC5\\u987B\\u4E3A \${e} \\u58F0\\u660E\\u552F\\u4E00 Individual Coding Plan Provider\`);return n[0]}
r(Kgi,"resolveStandaloneCodingPlanProvider");
function fW(e){let t=e.trim();if(!t)throw new Error("Standalone Account Provider ID \\u4E0D\\u80FD\\u4E3A\\u7A7A");return\`account-provider:\${t}:identity\`}
r(fW,"standaloneAccountIdentityCredentialKey");
function Ygi(e){return{async refreshBeforeModelRequest(c){let u=c.providerId.trim(),f=c.accountAccess,_=f.accountType;if(!f||f.mode!=="individual-coding-plan")throw new Error(\`Standalone Account Provider \\u8BF7\\u6C42\\u8EAB\\u4EFD\\u65E0\\u6548: \${u}\`);let h=(await e.load(fW(u)))?.trim();if(!h)throw new Error("Standalone Account Provider \\u51ED\\u8BC1\\u5DF2\\u7ECF\\u5931\\u6548: "+u);let v=bX(u),S=await e.load(v),k=J3n(_),I=S==="manual"?void 0:(await e.load(k))?.trim(),C=S==="oauth"||S!=="manual"&&!!I,R,D,B;if(C){R=await exchange()}else if(S==="manual"){R=(await e.load($5e({providerId:u,accountIdentity:h})))?.trim()}if(!C&&S!=="manual")throw new Error("project_token_login_required");if(c.abortSignal?.throwIfAborted(),!R)throw new Error(\`Standalone Account Provider \\u7F3A\\u5C11\\u8BF7\\u6C42\\u51ED\\u636E: \${u}\`);return{headersApplied:!0,requestAuth:{apiKey:R,...D?{apiKeyId:D}:{},...B?{accountScope:B}:{}}}}}}
r(Ygi,"createStandaloneProviderRuntimeHeadersPort");
`;

test("patched fixture admits Start Plan providers and is idempotent", () => {
expect(hasRuntimeStartPlan(fixture)).toBe(false);
const patched = patchRuntimeStartPlan(fixture);
expect(hasRuntimeStartPlan(patched)).toBe(true);
expect(patchRuntimeStartPlan(patched)).toBe(patched);
expect(patched).toContain('c.mode==="individual-coding-plan"||c.mode==="start-plan"');
expect(patched).toContain('!/-start-plan$/.test(o.providerId)');
expect(patched).toContain('t.replace(/-start-plan$/u,"-individual-coding-plan")');
expect(patched).toContain('f.mode!=="individual-coding-plan"&&f.mode!=="start-plan"');
expect(patched).toContain('(await e.load("zcodejwttoken"))?.trim()');
});

test("Start Plan identity resolves to the family Coding Plan identity credential", () => {
const patched = patchRuntimeStartPlan(fixture);
// Label/env stubs only; the function under test touches none of the others.
const fW = new Function("require", "r", "VG", "jM", "Z3n", "bX", "J3n", "$5e", `${patched}\nreturn fW;`)(
(id: string) => (id === "node:crypto" ? {} : undefined),
() => {},
"VG", class {}, async () => [],
() => "auth-source", () => "oauth", () => "api-key",
) as (id: string) => string;
expect(fW("account:zai-individual-coding-plan")).toBe("account-provider:account:zai-individual-coding-plan:identity");
expect(fW("account:zai-start-plan")).toBe("account-provider:account:zai-individual-coding-plan:identity");
expect(fW("account:bigmodel-start-plan")).toBe("account-provider:account:bigmodel-individual-coding-plan:identity");
expect(fW("account:zai-team-coding-plan")).toBe("account-provider:account:zai-team-coding-plan:identity");
});

test("synced runtime ships standalone Start Plan support", async () => {
const runtime = await readFile(join(root, "vendor/zcode.cjs"), "utf8");
expect(hasRuntimeStartPlan(runtime)).toBe(true);
expect(patchRuntimeStartPlan(runtime)).toBe(runtime);
});