pi2dsh 0.3.0 → 0.3.1
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/README.md +16 -1
- package/README.zh.md +16 -1
- package/dist/compat/pi-ai.d.mts +10 -1
- package/dist/compat/pi-ai.d.mts.map +1 -1
- package/dist/compat/pi-ai.mjs +2 -2918
- package/dist/credentials-oauth.d.mts +27 -0
- package/dist/credentials-oauth.d.mts.map +1 -0
- package/dist/credentials-oauth.mjs +91 -0
- package/dist/credentials-oauth.mjs.map +1 -0
- package/dist/host.mjs +3 -1
- package/dist/host.mjs.map +1 -1
- package/dist/oauth-bridge-BpPrppjR.mjs +359 -0
- package/dist/oauth-bridge-BpPrppjR.mjs.map +1 -0
- package/dist/pi-ai-CWFlgigJ.mjs +2947 -0
- package/dist/pi-ai-CWFlgigJ.mjs.map +1 -0
- package/dist/{runtime-oLd2EInK.mjs → runtime-6DefpI6h.mjs} +11 -355
- package/dist/runtime-6DefpI6h.mjs.map +1 -0
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +1 -1
- package/package.json +11 -1
- package/dist/compat/pi-ai.mjs.map +0 -1
- package/dist/runtime-oLd2EInK.mjs.map +0 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
import { CredentialInfo, CredentialProvider, CredentialRef, ResolvedCredential } from "@deepseek-ai/dsh-credentials";
|
|
3
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
4
|
+
//#region src/credentials-oauth.d.ts
|
|
5
|
+
type UnknownRecord = Record<string, unknown>;
|
|
6
|
+
declare function oauthCredentialRef(providerId: string): string;
|
|
7
|
+
interface PiOAuthCredentialProviderOptions {
|
|
8
|
+
/** Path to the Pi-format auth.json; defaults to `$agentDir/auth.json`. */
|
|
9
|
+
authPath?: string;
|
|
10
|
+
/** Additional provider configs (id → config with an oauth block), e.g. from packages that registered providers. */
|
|
11
|
+
providers?: ReadonlyMap<string, UnknownRecord>;
|
|
12
|
+
}
|
|
13
|
+
declare class PiOAuthCredentialProvider extends CredentialProvider {
|
|
14
|
+
private readonly oauthStore;
|
|
15
|
+
private readonly extraProviders;
|
|
16
|
+
constructor(ctx: Context, options?: PiOAuthCredentialProviderOptions);
|
|
17
|
+
private oauthConfigFor;
|
|
18
|
+
resolve(ref: CredentialRef): Promise<ResolvedCredential | undefined>;
|
|
19
|
+
describe(ref: CredentialRef): Promise<CredentialInfo>;
|
|
20
|
+
set(ref: CredentialRef, _value: string): Promise<void>;
|
|
21
|
+
unset(ref: CredentialRef): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
declare const name = "pi2dsh-credentials-oauth";
|
|
24
|
+
declare function apply(ctx: Context, config?: PiOAuthCredentialProviderOptions): void;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { PiOAuthCredentialProvider, PiOAuthCredentialProviderOptions, apply, name, oauthCredentialRef };
|
|
27
|
+
//# sourceMappingURL=credentials-oauth.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials-oauth.d.mts","names":[],"sources":["../src/credentials-oauth.ts"],"mappings":";;;;KAOK,gBAAgB;iBAgBL,mBAAmB;UASlB;;EAEf;;EAEA,YAAY,oBAAoB;;cAGrB,kCAAkC;mBAC5B;mBACA;EAEL,YAAA,KAAK,SAAS,UAAS;UAM3B;EAQF,QAAQ,KAAK,gBAAgB,QAAQ;EAiBrC,SAAS,KAAK,gBAAgB,QAAQ;EAUtC,IAAI,KAAK,eAAe,iBAAiB;EAIzC,MAAM,KAAK,gBAAgB;;cAUtB;iBAEG,MAAM,KAAK,SAAS,SAAQ"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
|
|
2
|
+
import { t as getAgentDir } from "./pi-config-shim-CZ1wFzqM.mjs";
|
|
3
|
+
import "./pi-coding-agent-Z1hTs61i.mjs";
|
|
4
|
+
import { a as storedOAuthCredential, i as resolveOAuthApiKey, r as providerSupportsOAuth, t as FileCredentialStore } from "./oauth-bridge-BpPrppjR.mjs";
|
|
5
|
+
import { r as builtinProviders } from "./pi-ai-CWFlgigJ.mjs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { CredentialProvider } from "@deepseek-ai/dsh-credentials";
|
|
8
|
+
//#region src/credentials-oauth.ts
|
|
9
|
+
const OAUTH_REF_PREFIX = "PI2DSH_OAUTH_";
|
|
10
|
+
function oauthCredentialRef(providerId) {
|
|
11
|
+
return `${OAUTH_REF_PREFIX}${providerId.toUpperCase().replaceAll("-", "_")}`;
|
|
12
|
+
}
|
|
13
|
+
function providerIdOfRef(ref) {
|
|
14
|
+
if (!ref.startsWith(OAUTH_REF_PREFIX)) return void 0;
|
|
15
|
+
return ref.slice(13).toLowerCase().replaceAll("_", "-");
|
|
16
|
+
}
|
|
17
|
+
var PiOAuthCredentialProvider = class extends CredentialProvider {
|
|
18
|
+
oauthStore;
|
|
19
|
+
extraProviders;
|
|
20
|
+
constructor(ctx, options = {}) {
|
|
21
|
+
super(ctx);
|
|
22
|
+
this.oauthStore = new FileCredentialStore(options.authPath ?? join(getAgentDir(), "auth.json"));
|
|
23
|
+
this.extraProviders = options.providers ?? /* @__PURE__ */ new Map();
|
|
24
|
+
}
|
|
25
|
+
oauthConfigFor(providerId) {
|
|
26
|
+
const registered = this.extraProviders.get(providerId);
|
|
27
|
+
if (providerSupportsOAuth(registered)) return registered;
|
|
28
|
+
const builtin = builtinProviders().find((provider) => provider.id === providerId);
|
|
29
|
+
if (builtin === void 0) return void 0;
|
|
30
|
+
return {
|
|
31
|
+
name: builtin.name,
|
|
32
|
+
baseUrl: builtin.baseUrl,
|
|
33
|
+
oauth: builtin.auth.oauth
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
async resolve(ref) {
|
|
37
|
+
const providerId = providerIdOfRef(String(ref));
|
|
38
|
+
if (providerId === void 0) {
|
|
39
|
+
const value = process.env[String(ref)];
|
|
40
|
+
return value !== void 0 && value.length > 0 ? {
|
|
41
|
+
value,
|
|
42
|
+
source: "env"
|
|
43
|
+
} : void 0;
|
|
44
|
+
}
|
|
45
|
+
const config = this.oauthConfigFor(providerId);
|
|
46
|
+
if (config === void 0) return void 0;
|
|
47
|
+
const value = await resolveOAuthApiKey({
|
|
48
|
+
providerId,
|
|
49
|
+
providerName: typeof config.name === "string" ? config.name : providerId,
|
|
50
|
+
providerConfig: config,
|
|
51
|
+
store: this.oauthStore
|
|
52
|
+
});
|
|
53
|
+
return value !== void 0 && value.length > 0 ? {
|
|
54
|
+
value,
|
|
55
|
+
source: "pi-oauth"
|
|
56
|
+
} : void 0;
|
|
57
|
+
}
|
|
58
|
+
async describe(ref) {
|
|
59
|
+
const providerId = providerIdOfRef(String(ref));
|
|
60
|
+
if (providerId === void 0) {
|
|
61
|
+
const value = process.env[String(ref)];
|
|
62
|
+
return {
|
|
63
|
+
configured: value !== void 0 && value.length > 0,
|
|
64
|
+
...value ? { source: "env" } : {},
|
|
65
|
+
writable: false
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const stored = await storedOAuthCredential(this.oauthStore, providerId);
|
|
69
|
+
return {
|
|
70
|
+
configured: stored !== void 0,
|
|
71
|
+
...stored !== void 0 ? { source: "pi-oauth" } : {},
|
|
72
|
+
writable: false
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
async set(ref, _value) {
|
|
76
|
+
throw new Error(`credential ${String(ref)} is read-only here: OAuth tokens are managed by /login, environment values by the shell`);
|
|
77
|
+
}
|
|
78
|
+
async unset(ref) {
|
|
79
|
+
const providerId = providerIdOfRef(String(ref));
|
|
80
|
+
if (providerId === void 0) throw new Error(`credential ${String(ref)} is read-only here: environment values are managed by the shell`);
|
|
81
|
+
await this.oauthStore.delete(providerId, void 0);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
const name = "pi2dsh-credentials-oauth";
|
|
85
|
+
function apply(ctx, config = {}) {
|
|
86
|
+
new PiOAuthCredentialProvider(ctx, config);
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
export { PiOAuthCredentialProvider, apply, name, oauthCredentialRef };
|
|
90
|
+
|
|
91
|
+
//# sourceMappingURL=credentials-oauth.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials-oauth.mjs","names":[],"sources":["../src/credentials-oauth.ts"],"sourcesContent":["import { join } from 'node:path'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { CredentialProvider, type CredentialInfo, type CredentialRef, type ResolvedCredential } from '@deepseek-ai/dsh-credentials'\nimport { FileCredentialStore, providerSupportsOAuth, resolveOAuthApiKey, storedOAuthCredential } from './oauth-bridge.js'\nimport { builtinProviders } from './compat/pi-ai.js'\nimport { getAgentDir } from './compat/pi-coding-agent.js'\n\ntype UnknownRecord = Record<string, unknown>\n\n// L4 of the OAuth host seam: a standard dsh-credentials provider that serves\n// Pi OAuth tokens to DSH's native LLM request path. A route configured as\n// `apiKeyEnv: PI2DSH_OAUTH_OPENAI_CODEX` resolves per request through this\n// provider — Pi's double-checked-lock refresh runs on every resolution, so a\n// rotated token reaches the next model call with no restart, exactly the\n// per-operation semantics the credentials seam demands. Every other reference\n// falls through to the process environment, so one provider instance serves a\n// whole composition.\n//\n// Reference convention: PI2DSH_OAUTH_<PROVIDER_ID> where the provider id is\n// upper-cased with `-` as `_` (openai-codex → PI2DSH_OAUTH_OPENAI_CODEX).\n\nconst OAUTH_REF_PREFIX = 'PI2DSH_OAUTH_'\n\nexport function oauthCredentialRef(providerId: string): string {\n return `${OAUTH_REF_PREFIX}${providerId.toUpperCase().replaceAll('-', '_')}`\n}\n\nfunction providerIdOfRef(ref: string): string | undefined {\n if (!ref.startsWith(OAUTH_REF_PREFIX)) return undefined\n return ref.slice(OAUTH_REF_PREFIX.length).toLowerCase().replaceAll('_', '-')\n}\n\nexport interface PiOAuthCredentialProviderOptions {\n /** Path to the Pi-format auth.json; defaults to `$agentDir/auth.json`. */\n authPath?: string\n /** Additional provider configs (id → config with an oauth block), e.g. from packages that registered providers. */\n providers?: ReadonlyMap<string, UnknownRecord>\n}\n\nexport class PiOAuthCredentialProvider extends CredentialProvider {\n private readonly oauthStore: FileCredentialStore\n private readonly extraProviders: ReadonlyMap<string, UnknownRecord>\n\n constructor(ctx: Context, options: PiOAuthCredentialProviderOptions = {}) {\n super(ctx)\n this.oauthStore = new FileCredentialStore(options.authPath ?? join(getAgentDir(), 'auth.json'))\n this.extraProviders = options.providers ?? new Map()\n }\n\n private oauthConfigFor(providerId: string): UnknownRecord | undefined {\n const registered = this.extraProviders.get(providerId)\n if (providerSupportsOAuth(registered)) return registered\n const builtin = builtinProviders().find(provider => provider.id === providerId)\n if (builtin === undefined) return undefined\n return { name: builtin.name, baseUrl: builtin.baseUrl, oauth: builtin.auth.oauth }\n }\n\n async resolve(ref: CredentialRef): Promise<ResolvedCredential | undefined> {\n const providerId = providerIdOfRef(String(ref))\n if (providerId === undefined) {\n const value = process.env[String(ref)]\n return value !== undefined && value.length > 0 ? { value, source: 'env' } : undefined\n }\n const config = this.oauthConfigFor(providerId)\n if (config === undefined) return undefined\n const value = await resolveOAuthApiKey({\n providerId,\n providerName: typeof config.name === 'string' ? config.name : providerId,\n providerConfig: config,\n store: this.oauthStore,\n })\n return value !== undefined && value.length > 0 ? { value, source: 'pi-oauth' } : undefined\n }\n\n async describe(ref: CredentialRef): Promise<CredentialInfo> {\n const providerId = providerIdOfRef(String(ref))\n if (providerId === undefined) {\n const value = process.env[String(ref)]\n return { configured: value !== undefined && value.length > 0, ...(value ? { source: 'env' } : {}), writable: false }\n }\n const stored = await storedOAuthCredential(this.oauthStore, providerId)\n return { configured: stored !== undefined, ...(stored !== undefined ? { source: 'pi-oauth' } : {}), writable: false }\n }\n\n async set(ref: CredentialRef, _value: string): Promise<void> {\n throw new Error(`credential ${String(ref)} is read-only here: OAuth tokens are managed by /login, environment values by the shell`)\n }\n\n async unset(ref: CredentialRef): Promise<void> {\n const providerId = providerIdOfRef(String(ref))\n if (providerId === undefined) {\n throw new Error(`credential ${String(ref)} is read-only here: environment values are managed by the shell`)\n }\n // Logging out is a legitimate unset: drop the stored token.\n await this.oauthStore.delete(providerId, undefined)\n }\n}\n\nexport const name = 'pi2dsh-credentials-oauth'\n\nexport function apply(ctx: Context, config: PiOAuthCredentialProviderOptions = {}): void {\n // The Service constructor registers itself as ctx.credentials.\n void new PiOAuthCredentialProvider(ctx, config)\n}\n"],"mappings":";;;;;;;;AAqBA,MAAM,mBAAmB;AAEzB,SAAgB,mBAAmB,YAA4B;CAC7D,OAAO,GAAG,mBAAmB,WAAW,YAAY,CAAC,CAAC,WAAW,KAAK,GAAG;AAC3E;AAEA,SAAS,gBAAgB,KAAiC;CACxD,IAAI,CAAC,IAAI,WAAW,gBAAgB,GAAG,OAAO,KAAA;CAC9C,OAAO,IAAI,MAAM,EAAuB,CAAC,CAAC,YAAY,CAAC,CAAC,WAAW,KAAK,GAAG;AAC7E;AASA,IAAa,4BAAb,cAA+C,mBAAmB;CAChE;CACA;CAEA,YAAY,KAAc,UAA4C,CAAC,GAAG;EACxE,MAAM,GAAG;EACT,KAAK,aAAa,IAAI,oBAAoB,QAAQ,YAAY,KAAK,YAAY,GAAG,WAAW,CAAC;EAC9F,KAAK,iBAAiB,QAAQ,6BAAa,IAAI,IAAI;CACrD;CAEA,eAAuB,YAA+C;EACpE,MAAM,aAAa,KAAK,eAAe,IAAI,UAAU;EACrD,IAAI,sBAAsB,UAAU,GAAG,OAAO;EAC9C,MAAM,UAAU,iBAAiB,CAAC,CAAC,MAAK,aAAY,SAAS,OAAO,UAAU;EAC9E,IAAI,YAAY,KAAA,GAAW,OAAO,KAAA;EAClC,OAAO;GAAE,MAAM,QAAQ;GAAM,SAAS,QAAQ;GAAS,OAAO,QAAQ,KAAK;EAAM;CACnF;CAEA,MAAM,QAAQ,KAA6D;EACzE,MAAM,aAAa,gBAAgB,OAAO,GAAG,CAAC;EAC9C,IAAI,eAAe,KAAA,GAAW;GAC5B,MAAM,QAAQ,QAAQ,IAAI,OAAO,GAAG;GACpC,OAAO,UAAU,KAAA,KAAa,MAAM,SAAS,IAAI;IAAE;IAAO,QAAQ;GAAM,IAAI,KAAA;EAC9E;EACA,MAAM,SAAS,KAAK,eAAe,UAAU;EAC7C,IAAI,WAAW,KAAA,GAAW,OAAO,KAAA;EACjC,MAAM,QAAQ,MAAM,mBAAmB;GACrC;GACA,cAAc,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO;GAC9D,gBAAgB;GAChB,OAAO,KAAK;EACd,CAAC;EACD,OAAO,UAAU,KAAA,KAAa,MAAM,SAAS,IAAI;GAAE;GAAO,QAAQ;EAAW,IAAI,KAAA;CACnF;CAEA,MAAM,SAAS,KAA6C;EAC1D,MAAM,aAAa,gBAAgB,OAAO,GAAG,CAAC;EAC9C,IAAI,eAAe,KAAA,GAAW;GAC5B,MAAM,QAAQ,QAAQ,IAAI,OAAO,GAAG;GACpC,OAAO;IAAE,YAAY,UAAU,KAAA,KAAa,MAAM,SAAS;IAAG,GAAI,QAAQ,EAAE,QAAQ,MAAM,IAAI,CAAC;IAAI,UAAU;GAAM;EACrH;EACA,MAAM,SAAS,MAAM,sBAAsB,KAAK,YAAY,UAAU;EACtE,OAAO;GAAE,YAAY,WAAW,KAAA;GAAW,GAAI,WAAW,KAAA,IAAY,EAAE,QAAQ,WAAW,IAAI,CAAC;GAAI,UAAU;EAAM;CACtH;CAEA,MAAM,IAAI,KAAoB,QAA+B;EAC3D,MAAM,IAAI,MAAM,cAAc,OAAO,GAAG,EAAE,wFAAwF;CACpI;CAEA,MAAM,MAAM,KAAmC;EAC7C,MAAM,aAAa,gBAAgB,OAAO,GAAG,CAAC;EAC9C,IAAI,eAAe,KAAA,GACjB,MAAM,IAAI,MAAM,cAAc,OAAO,GAAG,EAAE,gEAAgE;EAG5G,MAAM,KAAK,WAAW,OAAO,YAAY,KAAA,CAAS;CACpD;AACF;AAEA,MAAa,OAAO;AAEpB,SAAgB,MAAM,KAAc,SAA2C,CAAC,GAAS;CAEvF,IAAS,0BAA0B,KAAK,MAAM;AAChD"}
|
package/dist/host.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { t as applyPiPackage } from "./runtime-
|
|
2
|
+
import { t as applyPiPackage } from "./runtime-6DefpI6h.mjs";
|
|
3
3
|
import { t as resolvePiPackage } from "./source-0sA5z08z.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { cp, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
@@ -157,6 +157,8 @@ async function generateHostBundle(options) {
|
|
|
157
157
|
marked: "^16.4.1",
|
|
158
158
|
typebox: "^1.0.4",
|
|
159
159
|
tinyglobby: "^0.2.15",
|
|
160
|
+
"cross-spawn": "^7.0.6",
|
|
161
|
+
diff: "^9.0.0",
|
|
160
162
|
"@deepseek-ai/dsh-skill-filesystem": "^0.1.0-rc.6"
|
|
161
163
|
},
|
|
162
164
|
peerDependencies: {
|
package/dist/host.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.mjs","names":[],"sources":["../src/host.ts"],"sourcesContent":["// PiHostOnDSH: one DSH plugin that hosts unmodified Pi packages.\n//\n// Instead of converting each package into a vendored bundle, the host bundle\n// declares Pi packages as ordinary npm dependencies; DSH's plugin manager\n// (pnpm) installs them, and at load time this module resolves each installed\n// package, discovers its Pi entry points, and mounts it through the same\n// package-agnostic runtime as converted bundles. One host, any package —\n// there is deliberately no per-package branching here.\n\nimport { readdir, readFile, stat, writeFile, mkdir, cp } from 'node:fs/promises'\nimport { createRequire } from 'node:module'\nimport { basename, dirname, join, relative } from 'node:path'\nimport { fileURLToPath, pathToFileURL } from 'node:url'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { applyPiPackage } from './runtime.js'\nimport { resolvePiPackage } from './source.js'\nimport type { GeneratedRuntimeManifest, ResolvedPiPackage } from './types.js'\n\ntype UnknownRecord = Record<string, unknown>\n\nexport interface PiHostPackageSpec {\n /** npm package name as installed in the host bundle's node_modules. */\n name: string\n /** Optional per-package config forwarded to the runtime. */\n config?: UnknownRecord\n}\n\nexport interface PiHostConfig {\n packages: Array<string | PiHostPackageSpec>\n}\n\nfunction parseFrontmatter(text: string): { attributes: Record<string, string>; body: string } {\n const normalized = text.replace(/\\r\\n?/gu, '\\n')\n if (!normalized.startsWith('---')) return { attributes: {}, body: normalized }\n const endIndex = normalized.indexOf('\\n---', 3)\n if (endIndex === -1) return { attributes: {}, body: normalized }\n const attributes: Record<string, string> = {}\n for (const line of normalized.slice(4, endIndex).split('\\n')) {\n const separator = line.indexOf(':')\n if (separator === -1) continue\n const key = line.slice(0, separator).trim()\n let value = line.slice(separator + 1).trim()\n if ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1)\n }\n if (key.length > 0) attributes[key] = value\n }\n return { attributes, body: normalized.slice(endIndex + 4).trim() }\n}\n\n/** Build a runtime manifest in place over an installed Pi package directory. */\nexport async function manifestForInstalled(pkg: ResolvedPiPackage): Promise<GeneratedRuntimeManifest> {\n const relativeTo = (file: string): string => relative(pkg.rootDir, file).replaceAll('\\\\', '/')\n\n const skillDirs = new Set<string>()\n for (const file of pkg.resources.skills) {\n // <dir>/<name>/SKILL.md contributes <dir>; a flat <dir>/<name>.md contributes <dir>.\n skillDirs.add(relativeTo(basename(file) === 'SKILL.md' ? dirname(dirname(file)) : dirname(file)))\n }\n\n const prompts: GeneratedRuntimeManifest['prompts'] = []\n const promptNames = new Set<string>()\n for (const source of pkg.resources.prompts) {\n const name = basename(source, '.md').toLowerCase().replace(/[^a-z0-9_-]+/gu, '-')\n if (promptNames.has(name)) throw new Error(`prompt command name collision in ${pkg.identity.name}: ${name}`)\n promptNames.add(name)\n const { attributes, body } = parseFrontmatter(await readFile(source, 'utf8'))\n const firstLine = body.split(/\\r?\\n/u).map(line => line.trim()).find(Boolean)\n prompts.push({\n name,\n description: attributes.description ?? firstLine ?? `Run migrated Pi prompt ${name}`,\n ...(attributes['argument-hint'] !== undefined ? { argumentHint: attributes['argument-hint'] } : {}),\n path: relativeTo(source),\n })\n }\n\n return {\n schemaVersion: 1,\n package: pkg.identity,\n extensions: pkg.resources.extensions.map(relativeTo),\n skillDirs: [...skillDirs].sort(),\n prompts,\n }\n}\n\nfunction normalizeSpecs(config: PiHostConfig): PiHostPackageSpec[] {\n const packages = Array.isArray(config?.packages) ? config.packages : []\n return packages.map(spec => (typeof spec === 'string' ? { name: spec } : spec))\n .filter(spec => typeof spec?.name === 'string' && spec.name.length > 0)\n}\n\nfunction resolveInstalledDir(anchor: string, packageName: string): string {\n const require = createRequire(anchor)\n return dirname(require.resolve(`${packageName}/package.json`))\n}\n\n/**\n * Mount every configured Pi package from the host bundle's own node_modules.\n * Packages that fail to mount report their error and do not take down the\n * host or their siblings — matching Pi's own per-extension error isolation.\n */\nexport async function applyPiHost(ctx: Context, config: PiHostConfig, anchor?: string): Promise<void> {\n const anchorPath = anchor ?? fileURLToPath(import.meta.url)\n const errors: Array<{ name: string; error: string }> = []\n for (const spec of normalizeSpecs(config)) {\n try {\n const dir = resolveInstalledDir(anchorPath, spec.name)\n const pkg = await resolvePiPackage(dir)\n try {\n const manifest = await manifestForInstalled(pkg)\n await applyPiPackage(ctx, {\n rootUrl: pathToFileURL(`${pkg.rootDir}/`),\n manifest,\n ...(spec.config === undefined ? {} : { config: spec.config }),\n })\n } finally {\n await pkg.dispose()\n }\n } catch (error) {\n errors.push({ name: spec.name, error: error instanceof Error ? error.message : String(error) })\n }\n }\n for (const failure of errors) {\n const log = (ctx as unknown as { logger?: { warn?(message: string): void } }).logger\n const warn = log?.warn?.bind(log) ?? console.warn\n warn(`[pi2dsh host] failed to mount ${failure.name}: ${failure.error}`)\n }\n if (errors.length > 0 && errors.length === normalizeSpecs(config).length) {\n throw new Error(`pi2dsh host mounted no packages; first failure: ${errors[0]!.name}: ${errors[0]!.error}`)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Host bundle generation\n// ---------------------------------------------------------------------------\n\nexport interface HostBundleOptions {\n outDir: string\n /** npm specs, e.g. \"@narumitw/pi-lsp@0.49.4\" or \"pi-ask-user\". */\n packages: string[]\n bundleName?: string\n}\n\nfunction splitSpec(spec: string): { name: string; range: string } {\n const at = spec.lastIndexOf('@')\n if (at > 0) return { name: spec.slice(0, at), range: spec.slice(at + 1) }\n return { name: spec, range: '*' }\n}\n\nasync function firstExisting(paths: string[]): Promise<string> {\n for (const path of paths) {\n try {\n await stat(path)\n return path\n } catch {\n // Try the next layout.\n }\n }\n throw new Error(`cannot locate pi2dsh runtime artifact; tried: ${paths.join(', ')}`)\n}\n\nasync function copyEmbeddedHostRuntime(outDir: string): Promise<void> {\n const moduleDir = dirname(fileURLToPath(import.meta.url))\n const hostSource = await firstExisting([\n join(moduleDir, 'host.mjs'),\n join(moduleDir, '../dist/host.mjs'),\n ])\n const distRoot = dirname(hostSource)\n const targetRoot = join(outDir, 'runtime')\n await mkdir(join(targetRoot, 'compat', 'vendor'), { recursive: true })\n for (const entry of await readdir(distRoot)) {\n if (entry.endsWith('.mjs') || entry.endsWith('.d.mts')) {\n await cp(join(distRoot, entry), join(targetRoot, entry))\n }\n }\n for (const sub of ['compat', join('compat', 'vendor')]) {\n const dir = join(distRoot, sub)\n try {\n for (const entry of await readdir(dir)) {\n if (entry.endsWith('.mjs')) await cp(join(dir, entry), join(targetRoot, sub, entry))\n }\n } catch {\n // dist layout without that subdirectory\n }\n }\n}\n\nexport async function generateHostBundle(options: HostBundleOptions): Promise<{ outDir: string; packageName: string }> {\n const specs = options.packages.map(splitSpec)\n if (specs.length === 0) throw new Error('host bundle requires at least one Pi package spec')\n const packageName = options.bundleName ?? 'dsh-pi-host'\n await mkdir(options.outDir, { recursive: true })\n await copyEmbeddedHostRuntime(options.outDir)\n\n const packageJson = {\n name: packageName,\n version: '0.1.0',\n description: `pi2dsh host bundle mounting ${specs.map(spec => spec.name).join(', ')} as native DSH plugins`,\n type: 'module',\n main: './index.js',\n files: ['index.js', 'cordis.patch.yml', 'runtime', 'PI2DSH-LICENSE', 'README.md'],\n dependencies: {\n ...Object.fromEntries(specs.map(spec => [spec.name, spec.range])),\n jiti: '^2.7.0',\n 'get-east-asian-width': '^1.6.0',\n marked: '^16.4.1',\n typebox: '^1.0.4',\n tinyglobby: '^0.2.15',\n '@deepseek-ai/dsh-skill-filesystem': '^0.1.0-rc.6',\n },\n peerDependencies: {\n '@deepseek-ai/dsh-llm': '^0.1.0-rc.6',\n '@deepseek-ai/dsh-system-prompt': '^0.1.0-rc.6',\n },\n keywords: ['dsh-plugin', 'deepseek-harness', 'pi-package', 'pi2dsh', 'pi-host'],\n license: 'MIT',\n dsh: { bundle: { patch: './cordis.patch.yml' } },\n }\n await writeFile(join(options.outDir, 'package.json'), `${JSON.stringify(packageJson, null, 2)}\\n`)\n\n const hostConfig: PiHostConfig = { packages: specs.map(spec => spec.name) }\n const indexSource = `import { applyPiHost } from './runtime/host.mjs'\\n\\n`\n + `export const name = ${JSON.stringify(packageName)}\\n`\n + `export const inject = ${JSON.stringify(['tools', 'systemPrompt', 'commands', 'skills'])}\\n\\n`\n + `const hostConfig = ${JSON.stringify(hostConfig, null, 2)}\\n\\n`\n + `export async function apply(ctx, config = {}) {\\n`\n + ` await applyPiHost(ctx, { ...hostConfig, ...config }, new URL(import.meta.url).pathname)\\n`\n + `}\\n`\n await writeFile(join(options.outDir, 'index.js'), indexSource)\n\n await writeFile(join(options.outDir, 'cordis.patch.yml'),\n `- insert:\\n - id: ${packageName}\\n name: ${JSON.stringify(packageName)}\\n`)\n\n const licenseSource = await firstExisting([\n join(dirname(fileURLToPath(import.meta.url)), 'compat/vendor/PI-LICENSE'),\n join(dirname(fileURLToPath(import.meta.url)), '../src/compat/vendor/PI-LICENSE'),\n ])\n await cp(licenseSource, join(options.outDir, 'PI2DSH-LICENSE'))\n\n await writeFile(join(options.outDir, 'README.md'),\n `# ${packageName}\\n\\n`\n + `A [pi2dsh](https://github.com/weijiafu14/pi2dsh) host bundle. The Pi packages below are installed as ordinary npm dependencies and mounted at load time — no per-package conversion, no vendored source snapshots.\\n\\n`\n + specs.map(spec => `- \\`${spec.name}@${spec.range}\\`\\n`).join('')\n + `\\nInstall:\\n\\n\\`\\`\\`sh\\ndsh plugin --profile headless add file:$PWD\\ndsh --profile headless --dump-config\\n\\`\\`\\`\\n`\n + `\\nThe packages execute their original Pi extension source inside the pi2dsh Host ABI; install only sources you trust. Run \\`pi2dsh inspect <package>\\` for each package's compatibility report.\\n`)\n\n return { outDir: options.outDir, packageName }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"host.mjs","names":[],"sources":["../src/host.ts"],"sourcesContent":["// PiHostOnDSH: one DSH plugin that hosts unmodified Pi packages.\n//\n// Instead of converting each package into a vendored bundle, the host bundle\n// declares Pi packages as ordinary npm dependencies; DSH's plugin manager\n// (pnpm) installs them, and at load time this module resolves each installed\n// package, discovers its Pi entry points, and mounts it through the same\n// package-agnostic runtime as converted bundles. One host, any package —\n// there is deliberately no per-package branching here.\n\nimport { readdir, readFile, stat, writeFile, mkdir, cp } from 'node:fs/promises'\nimport { createRequire } from 'node:module'\nimport { basename, dirname, join, relative } from 'node:path'\nimport { fileURLToPath, pathToFileURL } from 'node:url'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { applyPiPackage } from './runtime.js'\nimport { resolvePiPackage } from './source.js'\nimport type { GeneratedRuntimeManifest, ResolvedPiPackage } from './types.js'\n\ntype UnknownRecord = Record<string, unknown>\n\nexport interface PiHostPackageSpec {\n /** npm package name as installed in the host bundle's node_modules. */\n name: string\n /** Optional per-package config forwarded to the runtime. */\n config?: UnknownRecord\n}\n\nexport interface PiHostConfig {\n packages: Array<string | PiHostPackageSpec>\n}\n\nfunction parseFrontmatter(text: string): { attributes: Record<string, string>; body: string } {\n const normalized = text.replace(/\\r\\n?/gu, '\\n')\n if (!normalized.startsWith('---')) return { attributes: {}, body: normalized }\n const endIndex = normalized.indexOf('\\n---', 3)\n if (endIndex === -1) return { attributes: {}, body: normalized }\n const attributes: Record<string, string> = {}\n for (const line of normalized.slice(4, endIndex).split('\\n')) {\n const separator = line.indexOf(':')\n if (separator === -1) continue\n const key = line.slice(0, separator).trim()\n let value = line.slice(separator + 1).trim()\n if ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1)\n }\n if (key.length > 0) attributes[key] = value\n }\n return { attributes, body: normalized.slice(endIndex + 4).trim() }\n}\n\n/** Build a runtime manifest in place over an installed Pi package directory. */\nexport async function manifestForInstalled(pkg: ResolvedPiPackage): Promise<GeneratedRuntimeManifest> {\n const relativeTo = (file: string): string => relative(pkg.rootDir, file).replaceAll('\\\\', '/')\n\n const skillDirs = new Set<string>()\n for (const file of pkg.resources.skills) {\n // <dir>/<name>/SKILL.md contributes <dir>; a flat <dir>/<name>.md contributes <dir>.\n skillDirs.add(relativeTo(basename(file) === 'SKILL.md' ? dirname(dirname(file)) : dirname(file)))\n }\n\n const prompts: GeneratedRuntimeManifest['prompts'] = []\n const promptNames = new Set<string>()\n for (const source of pkg.resources.prompts) {\n const name = basename(source, '.md').toLowerCase().replace(/[^a-z0-9_-]+/gu, '-')\n if (promptNames.has(name)) throw new Error(`prompt command name collision in ${pkg.identity.name}: ${name}`)\n promptNames.add(name)\n const { attributes, body } = parseFrontmatter(await readFile(source, 'utf8'))\n const firstLine = body.split(/\\r?\\n/u).map(line => line.trim()).find(Boolean)\n prompts.push({\n name,\n description: attributes.description ?? firstLine ?? `Run migrated Pi prompt ${name}`,\n ...(attributes['argument-hint'] !== undefined ? { argumentHint: attributes['argument-hint'] } : {}),\n path: relativeTo(source),\n })\n }\n\n return {\n schemaVersion: 1,\n package: pkg.identity,\n extensions: pkg.resources.extensions.map(relativeTo),\n skillDirs: [...skillDirs].sort(),\n prompts,\n }\n}\n\nfunction normalizeSpecs(config: PiHostConfig): PiHostPackageSpec[] {\n const packages = Array.isArray(config?.packages) ? config.packages : []\n return packages.map(spec => (typeof spec === 'string' ? { name: spec } : spec))\n .filter(spec => typeof spec?.name === 'string' && spec.name.length > 0)\n}\n\nfunction resolveInstalledDir(anchor: string, packageName: string): string {\n const require = createRequire(anchor)\n return dirname(require.resolve(`${packageName}/package.json`))\n}\n\n/**\n * Mount every configured Pi package from the host bundle's own node_modules.\n * Packages that fail to mount report their error and do not take down the\n * host or their siblings — matching Pi's own per-extension error isolation.\n */\nexport async function applyPiHost(ctx: Context, config: PiHostConfig, anchor?: string): Promise<void> {\n const anchorPath = anchor ?? fileURLToPath(import.meta.url)\n const errors: Array<{ name: string; error: string }> = []\n for (const spec of normalizeSpecs(config)) {\n try {\n const dir = resolveInstalledDir(anchorPath, spec.name)\n const pkg = await resolvePiPackage(dir)\n try {\n const manifest = await manifestForInstalled(pkg)\n await applyPiPackage(ctx, {\n rootUrl: pathToFileURL(`${pkg.rootDir}/`),\n manifest,\n ...(spec.config === undefined ? {} : { config: spec.config }),\n })\n } finally {\n await pkg.dispose()\n }\n } catch (error) {\n errors.push({ name: spec.name, error: error instanceof Error ? error.message : String(error) })\n }\n }\n for (const failure of errors) {\n const log = (ctx as unknown as { logger?: { warn?(message: string): void } }).logger\n const warn = log?.warn?.bind(log) ?? console.warn\n warn(`[pi2dsh host] failed to mount ${failure.name}: ${failure.error}`)\n }\n if (errors.length > 0 && errors.length === normalizeSpecs(config).length) {\n throw new Error(`pi2dsh host mounted no packages; first failure: ${errors[0]!.name}: ${errors[0]!.error}`)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Host bundle generation\n// ---------------------------------------------------------------------------\n\nexport interface HostBundleOptions {\n outDir: string\n /** npm specs, e.g. \"@narumitw/pi-lsp@0.49.4\" or \"pi-ask-user\". */\n packages: string[]\n bundleName?: string\n}\n\nfunction splitSpec(spec: string): { name: string; range: string } {\n const at = spec.lastIndexOf('@')\n if (at > 0) return { name: spec.slice(0, at), range: spec.slice(at + 1) }\n return { name: spec, range: '*' }\n}\n\nasync function firstExisting(paths: string[]): Promise<string> {\n for (const path of paths) {\n try {\n await stat(path)\n return path\n } catch {\n // Try the next layout.\n }\n }\n throw new Error(`cannot locate pi2dsh runtime artifact; tried: ${paths.join(', ')}`)\n}\n\nasync function copyEmbeddedHostRuntime(outDir: string): Promise<void> {\n const moduleDir = dirname(fileURLToPath(import.meta.url))\n const hostSource = await firstExisting([\n join(moduleDir, 'host.mjs'),\n join(moduleDir, '../dist/host.mjs'),\n ])\n const distRoot = dirname(hostSource)\n const targetRoot = join(outDir, 'runtime')\n await mkdir(join(targetRoot, 'compat', 'vendor'), { recursive: true })\n for (const entry of await readdir(distRoot)) {\n if (entry.endsWith('.mjs') || entry.endsWith('.d.mts')) {\n await cp(join(distRoot, entry), join(targetRoot, entry))\n }\n }\n for (const sub of ['compat', join('compat', 'vendor')]) {\n const dir = join(distRoot, sub)\n try {\n for (const entry of await readdir(dir)) {\n if (entry.endsWith('.mjs')) await cp(join(dir, entry), join(targetRoot, sub, entry))\n }\n } catch {\n // dist layout without that subdirectory\n }\n }\n}\n\nexport async function generateHostBundle(options: HostBundleOptions): Promise<{ outDir: string; packageName: string }> {\n const specs = options.packages.map(splitSpec)\n if (specs.length === 0) throw new Error('host bundle requires at least one Pi package spec')\n const packageName = options.bundleName ?? 'dsh-pi-host'\n await mkdir(options.outDir, { recursive: true })\n await copyEmbeddedHostRuntime(options.outDir)\n\n const packageJson = {\n name: packageName,\n version: '0.1.0',\n description: `pi2dsh host bundle mounting ${specs.map(spec => spec.name).join(', ')} as native DSH plugins`,\n type: 'module',\n main: './index.js',\n files: ['index.js', 'cordis.patch.yml', 'runtime', 'PI2DSH-LICENSE', 'README.md'],\n dependencies: {\n ...Object.fromEntries(specs.map(spec => [spec.name, spec.range])),\n jiti: '^2.7.0',\n 'get-east-asian-width': '^1.6.0',\n marked: '^16.4.1',\n typebox: '^1.0.4',\n tinyglobby: '^0.2.15',\n // The vendored Pi built-in tool constructors spawn through cross-spawn\n // and diff exactly as Pi does — same runtime set the convert bundle\n // carries (generator.ts keeps the sibling list).\n 'cross-spawn': '^7.0.6',\n diff: '^9.0.0',\n '@deepseek-ai/dsh-skill-filesystem': '^0.1.0-rc.6',\n },\n peerDependencies: {\n '@deepseek-ai/dsh-llm': '^0.1.0-rc.6',\n '@deepseek-ai/dsh-system-prompt': '^0.1.0-rc.6',\n },\n keywords: ['dsh-plugin', 'deepseek-harness', 'pi-package', 'pi2dsh', 'pi-host'],\n license: 'MIT',\n dsh: { bundle: { patch: './cordis.patch.yml' } },\n }\n await writeFile(join(options.outDir, 'package.json'), `${JSON.stringify(packageJson, null, 2)}\\n`)\n\n const hostConfig: PiHostConfig = { packages: specs.map(spec => spec.name) }\n const indexSource = `import { applyPiHost } from './runtime/host.mjs'\\n\\n`\n + `export const name = ${JSON.stringify(packageName)}\\n`\n + `export const inject = ${JSON.stringify(['tools', 'systemPrompt', 'commands', 'skills'])}\\n\\n`\n + `const hostConfig = ${JSON.stringify(hostConfig, null, 2)}\\n\\n`\n + `export async function apply(ctx, config = {}) {\\n`\n + ` await applyPiHost(ctx, { ...hostConfig, ...config }, new URL(import.meta.url).pathname)\\n`\n + `}\\n`\n await writeFile(join(options.outDir, 'index.js'), indexSource)\n\n await writeFile(join(options.outDir, 'cordis.patch.yml'),\n `- insert:\\n - id: ${packageName}\\n name: ${JSON.stringify(packageName)}\\n`)\n\n const licenseSource = await firstExisting([\n join(dirname(fileURLToPath(import.meta.url)), 'compat/vendor/PI-LICENSE'),\n join(dirname(fileURLToPath(import.meta.url)), '../src/compat/vendor/PI-LICENSE'),\n ])\n await cp(licenseSource, join(options.outDir, 'PI2DSH-LICENSE'))\n\n await writeFile(join(options.outDir, 'README.md'),\n `# ${packageName}\\n\\n`\n + `A [pi2dsh](https://github.com/weijiafu14/pi2dsh) host bundle. The Pi packages below are installed as ordinary npm dependencies and mounted at load time — no per-package conversion, no vendored source snapshots.\\n\\n`\n + specs.map(spec => `- \\`${spec.name}@${spec.range}\\`\\n`).join('')\n + `\\nInstall:\\n\\n\\`\\`\\`sh\\ndsh plugin --profile headless add file:$PWD\\ndsh --profile headless --dump-config\\n\\`\\`\\`\\n`\n + `\\nThe packages execute their original Pi extension source inside the pi2dsh Host ABI; install only sources you trust. Run \\`pi2dsh inspect <package>\\` for each package's compatibility report.\\n`)\n\n return { outDir: options.outDir, packageName }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
|
|
2
|
+
import { mkdir, rename, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
//#region src/compat/vendor/pi-ai-abort.ts
|
|
6
|
+
function abortReason(signal) {
|
|
7
|
+
if (signal.reason !== void 0) return signal.reason;
|
|
8
|
+
const error = /* @__PURE__ */ new Error("The operation was aborted");
|
|
9
|
+
error.name = "AbortError";
|
|
10
|
+
return error;
|
|
11
|
+
}
|
|
12
|
+
/** Create an operation-local signal for public APIs whose signal is optional. */
|
|
13
|
+
function operationSignal(signal) {
|
|
14
|
+
return signal ?? new AbortController().signal;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Stop waiting for an operation when its signal aborts while continuing to
|
|
18
|
+
* observe the abandoned promise so a later rejection is always handled.
|
|
19
|
+
*/
|
|
20
|
+
function raceWithAbortSignal(operation, signal) {
|
|
21
|
+
if (signal.aborted) {
|
|
22
|
+
operation.catch(() => {});
|
|
23
|
+
return Promise.reject(abortReason(signal));
|
|
24
|
+
}
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
let settled = false;
|
|
27
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
28
|
+
const onAbort = () => {
|
|
29
|
+
if (settled) return;
|
|
30
|
+
settled = true;
|
|
31
|
+
cleanup();
|
|
32
|
+
reject(abortReason(signal));
|
|
33
|
+
};
|
|
34
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
35
|
+
operation.then((value) => {
|
|
36
|
+
if (settled) return;
|
|
37
|
+
settled = true;
|
|
38
|
+
cleanup();
|
|
39
|
+
resolve(value);
|
|
40
|
+
}, (error) => {
|
|
41
|
+
if (settled) return;
|
|
42
|
+
settled = true;
|
|
43
|
+
cleanup();
|
|
44
|
+
reject(error);
|
|
45
|
+
});
|
|
46
|
+
if (signal.aborted) onAbort();
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/compat/vendor/pi-ai-credential-store.ts
|
|
51
|
+
/**
|
|
52
|
+
* Default in-memory credential store. Apps inject persistent stores.
|
|
53
|
+
* Keyed by `Provider.id`, one credential per provider; see `CredentialStore`.
|
|
54
|
+
* Writes are serialized per provider through a promise chain.
|
|
55
|
+
*/
|
|
56
|
+
var InMemoryCredentialStore = class {
|
|
57
|
+
credentials = /* @__PURE__ */ new Map();
|
|
58
|
+
chains = /* @__PURE__ */ new Map();
|
|
59
|
+
/** Serialize tasks per provider id without releasing the chain before active work settles. */
|
|
60
|
+
enqueue(providerId, task, options) {
|
|
61
|
+
const signal = operationSignal(options?.signal);
|
|
62
|
+
const previous = this.chains.get(providerId) ?? Promise.resolve();
|
|
63
|
+
const queued = (async () => {
|
|
64
|
+
await previous.catch(() => {});
|
|
65
|
+
signal.throwIfAborted();
|
|
66
|
+
return task();
|
|
67
|
+
})();
|
|
68
|
+
const tail = queued.catch(() => {});
|
|
69
|
+
this.chains.set(providerId, tail);
|
|
70
|
+
tail.then(() => {
|
|
71
|
+
if (this.chains.get(providerId) === tail) this.chains.delete(providerId);
|
|
72
|
+
});
|
|
73
|
+
return raceWithAbortSignal(queued, signal);
|
|
74
|
+
}
|
|
75
|
+
async read(providerId, options) {
|
|
76
|
+
options?.signal?.throwIfAborted();
|
|
77
|
+
return this.credentials.get(providerId);
|
|
78
|
+
}
|
|
79
|
+
async list(options) {
|
|
80
|
+
options?.signal?.throwIfAborted();
|
|
81
|
+
return [...this.credentials].map(([providerId, credential]) => ({
|
|
82
|
+
providerId,
|
|
83
|
+
type: credential.type
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
modify(providerId, fn, options) {
|
|
87
|
+
return this.enqueue(providerId, async () => {
|
|
88
|
+
const current = this.credentials.get(providerId);
|
|
89
|
+
const next = await fn(current);
|
|
90
|
+
options?.signal?.throwIfAborted();
|
|
91
|
+
if (next !== void 0) this.credentials.set(providerId, next);
|
|
92
|
+
return next ?? current;
|
|
93
|
+
}, options);
|
|
94
|
+
}
|
|
95
|
+
delete(providerId, options) {
|
|
96
|
+
return this.enqueue(providerId, async () => {
|
|
97
|
+
this.credentials.delete(providerId);
|
|
98
|
+
}, options);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/compat/vendor/pi-ai-diagnostics.ts
|
|
103
|
+
function formatThrownValue(value) {
|
|
104
|
+
if (value instanceof Error) return value.message || value.name;
|
|
105
|
+
if (typeof value === "string") return value;
|
|
106
|
+
return String(value);
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/compat/vendor/pi-ai-auth-resolve.ts
|
|
110
|
+
var ModelsError = class extends Error {
|
|
111
|
+
code;
|
|
112
|
+
constructor(code, message, options) {
|
|
113
|
+
super(withCauseDetail(message, options?.cause), options);
|
|
114
|
+
this.name = "ModelsError";
|
|
115
|
+
this.code = code;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
/** Callers surface `error.message` only, so keep the underlying reason in it. */
|
|
119
|
+
function withCauseDetail(message, cause) {
|
|
120
|
+
if (cause === void 0 || cause === null) return message;
|
|
121
|
+
const detail = formatThrownValue(cause).trim();
|
|
122
|
+
if (!detail || message.includes(detail)) return message;
|
|
123
|
+
return `${message}: ${detail}`;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Auth resolution shared by the `Models` and `ImagesModels` collections.
|
|
127
|
+
* A stored credential owns the provider: ambient/env is consulted only when
|
|
128
|
+
* nothing is stored. No silent env fallback after a failed refresh or for a
|
|
129
|
+
* credential type without a matching handler.
|
|
130
|
+
*/
|
|
131
|
+
function resolveProviderAuth(provider, credentials, authContext, overrides) {
|
|
132
|
+
const signal = operationSignal(overrides?.signal);
|
|
133
|
+
return raceWithAbortSignal(resolveProviderAuthWithSignal(provider, credentials, authContext, overrides, signal), signal);
|
|
134
|
+
}
|
|
135
|
+
async function resolveProviderAuthWithSignal(provider, credentials, authContext, overrides, signal) {
|
|
136
|
+
signal.throwIfAborted();
|
|
137
|
+
const requestAuthContext = overrides?.env ? overlayEnvAuthContext(authContext, overrides.env) : authContext;
|
|
138
|
+
if (overrides?.apiKey !== void 0 && provider.auth.apiKey) return resolveApiKey(requestAuthContext, provider.auth.apiKey, provider.id, {
|
|
139
|
+
type: "api_key",
|
|
140
|
+
key: overrides.apiKey,
|
|
141
|
+
env: overrides.env
|
|
142
|
+
}, signal);
|
|
143
|
+
const stored = await readCredential(credentials, provider.id, signal);
|
|
144
|
+
if (stored) {
|
|
145
|
+
if (stored.type === "oauth" && provider.auth.oauth) return resolveStoredOAuth(credentials, provider.id, provider.auth.oauth, stored, signal, overrides?.minOAuthValidityMs);
|
|
146
|
+
if (stored.type === "api_key" && provider.auth.apiKey) {
|
|
147
|
+
const credential = overrides?.env ? {
|
|
148
|
+
...stored,
|
|
149
|
+
env: {
|
|
150
|
+
...stored.env,
|
|
151
|
+
...overrides.env
|
|
152
|
+
}
|
|
153
|
+
} : stored;
|
|
154
|
+
return resolveApiKey(requestAuthContext, provider.auth.apiKey, provider.id, credential, signal);
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
return provider.auth.apiKey ? resolveApiKey(requestAuthContext, provider.auth.apiKey, provider.id, void 0, signal) : void 0;
|
|
159
|
+
}
|
|
160
|
+
function overlayEnvAuthContext(base, env) {
|
|
161
|
+
return {
|
|
162
|
+
env: async (name) => env[name] || await base.env(name),
|
|
163
|
+
fileExists: (path) => base.fileExists(path)
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const DEFAULT_OAUTH_MINIMUM_VALIDITY_MS = 3e5;
|
|
167
|
+
const DEFAULT_OAUTH_REFRESH_TIMEOUT_MS = 15e3;
|
|
168
|
+
/**
|
|
169
|
+
* OAuth resolution with double-checked locking: tokens with less than five
|
|
170
|
+
* minutes remaining lock, re-check expiry under the lock, refresh once
|
|
171
|
+
* globally, and persist the rotated credential before release.
|
|
172
|
+
*/
|
|
173
|
+
async function resolveStoredOAuth(credentials, providerId, oauth, stored, signal, minOAuthValidityMs) {
|
|
174
|
+
const minimumValidityMs = Math.max(DEFAULT_OAUTH_MINIMUM_VALIDITY_MS, minOAuthValidityMs ?? 0);
|
|
175
|
+
const expiresSoon = (credential) => Date.now() + minimumValidityMs >= credential.expires;
|
|
176
|
+
let credential = stored;
|
|
177
|
+
if (expiresSoon(credential)) {
|
|
178
|
+
let post;
|
|
179
|
+
try {
|
|
180
|
+
post = await credentials.modify(providerId, async (current) => {
|
|
181
|
+
if (current?.type !== "oauth") return void 0;
|
|
182
|
+
if (!expiresSoon(current)) return void 0;
|
|
183
|
+
try {
|
|
184
|
+
const refreshSignal = AbortSignal.any([signal, AbortSignal.timeout(DEFAULT_OAUTH_REFRESH_TIMEOUT_MS)]);
|
|
185
|
+
return await oauth.refresh(current, refreshSignal);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
throw new ModelsError("oauth", `OAuth refresh failed for ${providerId}`, { cause: error });
|
|
188
|
+
}
|
|
189
|
+
}, { signal });
|
|
190
|
+
} catch (error) {
|
|
191
|
+
if (error instanceof ModelsError) throw error;
|
|
192
|
+
throw new ModelsError("auth", `Credential store modify failed for ${providerId}`, { cause: error });
|
|
193
|
+
}
|
|
194
|
+
if (post?.type !== "oauth") return void 0;
|
|
195
|
+
credential = post;
|
|
196
|
+
if (minOAuthValidityMs !== void 0 && expiresSoon(credential)) throw new ModelsError("oauth", `OAuth refresh returned a token that expires too soon for ${providerId}`);
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
return {
|
|
200
|
+
auth: await oauth.toAuth(credential),
|
|
201
|
+
source: "OAuth"
|
|
202
|
+
};
|
|
203
|
+
} catch (error) {
|
|
204
|
+
throw new ModelsError("oauth", `OAuth auth derivation failed for ${providerId}`, { cause: error });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async function resolveApiKey(authContext, apiKey, providerId, credential, signal) {
|
|
208
|
+
try {
|
|
209
|
+
return await apiKey.resolve({
|
|
210
|
+
ctx: authContext,
|
|
211
|
+
credential,
|
|
212
|
+
signal
|
|
213
|
+
});
|
|
214
|
+
} catch (error) {
|
|
215
|
+
throw new ModelsError("auth", `API key auth failed for provider ${providerId}`, { cause: error });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async function readCredential(credentials, providerId, signal) {
|
|
219
|
+
try {
|
|
220
|
+
return await credentials.read(providerId, { signal });
|
|
221
|
+
} catch (error) {
|
|
222
|
+
throw new ModelsError("auth", `Credential store read failed for ${providerId}`, { cause: error });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/compat/vendor/pi-oauth-adapt.ts
|
|
227
|
+
function adaptOAuth(config) {
|
|
228
|
+
return {
|
|
229
|
+
name: config.name,
|
|
230
|
+
isSubscription: config.isSubscription,
|
|
231
|
+
login: async (callbacks) => {
|
|
232
|
+
return {
|
|
233
|
+
...await config.login({
|
|
234
|
+
onAuth: (info) => callbacks.notify({
|
|
235
|
+
type: "auth_url",
|
|
236
|
+
...info
|
|
237
|
+
}),
|
|
238
|
+
onDeviceCode: (info) => callbacks.notify({
|
|
239
|
+
type: "device_code",
|
|
240
|
+
...info
|
|
241
|
+
}),
|
|
242
|
+
onPrompt: (prompt) => callbacks.prompt({
|
|
243
|
+
type: "text",
|
|
244
|
+
...prompt
|
|
245
|
+
}),
|
|
246
|
+
onProgress: (message) => callbacks.notify({
|
|
247
|
+
type: "progress",
|
|
248
|
+
message
|
|
249
|
+
}),
|
|
250
|
+
onManualCodeInput: () => callbacks.prompt({
|
|
251
|
+
type: "manual_code",
|
|
252
|
+
message: "Paste the authorization code"
|
|
253
|
+
}),
|
|
254
|
+
onSelect: (prompt) => callbacks.prompt({
|
|
255
|
+
type: "select",
|
|
256
|
+
...prompt
|
|
257
|
+
}),
|
|
258
|
+
signal: callbacks.signal
|
|
259
|
+
}),
|
|
260
|
+
type: "oauth"
|
|
261
|
+
};
|
|
262
|
+
},
|
|
263
|
+
refresh: async (credential, signal) => ({
|
|
264
|
+
...await config.refreshToken(credential, signal),
|
|
265
|
+
type: "oauth"
|
|
266
|
+
}),
|
|
267
|
+
toAuth: async (credential) => ({ apiKey: config.getApiKey(credential) })
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
//#endregion
|
|
271
|
+
//#region src/oauth-bridge.ts
|
|
272
|
+
var FileCredentialStore = class extends InMemoryCredentialStore {
|
|
273
|
+
#path;
|
|
274
|
+
constructor(path) {
|
|
275
|
+
super();
|
|
276
|
+
this.#path = path;
|
|
277
|
+
try {
|
|
278
|
+
const data = JSON.parse(readFileSync(path, "utf8"));
|
|
279
|
+
for (const [providerId, credential] of Object.entries(data)) this.credentials.set(providerId, credential);
|
|
280
|
+
} catch {}
|
|
281
|
+
}
|
|
282
|
+
get path() {
|
|
283
|
+
return this.#path;
|
|
284
|
+
}
|
|
285
|
+
async #persist() {
|
|
286
|
+
const credentials = this.credentials;
|
|
287
|
+
const data = Object.fromEntries(credentials);
|
|
288
|
+
await mkdir(dirname(this.#path), { recursive: true });
|
|
289
|
+
const temp = `${this.#path}.tmp-${process.pid}-${Math.floor(Math.random() * 1e9).toString(36)}`;
|
|
290
|
+
await writeFile(temp, `${JSON.stringify(data, null, 2)}\n`, { mode: 384 });
|
|
291
|
+
await rename(temp, this.#path);
|
|
292
|
+
}
|
|
293
|
+
modify(providerId, fn, options) {
|
|
294
|
+
return super.modify(providerId, async (current) => {
|
|
295
|
+
const next = await fn(current);
|
|
296
|
+
if (next !== void 0) {
|
|
297
|
+
this.credentials.set(providerId, next);
|
|
298
|
+
await this.#persist();
|
|
299
|
+
}
|
|
300
|
+
return next;
|
|
301
|
+
}, options);
|
|
302
|
+
}
|
|
303
|
+
delete(providerId, options) {
|
|
304
|
+
return super.delete(providerId, options).then(() => this.#persist());
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
function oauthInteraction(ui, signal) {
|
|
308
|
+
return {
|
|
309
|
+
signal: signal ?? new AbortController().signal,
|
|
310
|
+
async prompt(prompt) {
|
|
311
|
+
if (prompt.type === "select") {
|
|
312
|
+
const options = prompt.options ?? [];
|
|
313
|
+
const picked = await ui.select(prompt.message, options.map((option) => option.label));
|
|
314
|
+
return options.find((option) => option.label === picked)?.id ?? picked;
|
|
315
|
+
}
|
|
316
|
+
return ui.input(prompt.message, prompt.placeholder);
|
|
317
|
+
},
|
|
318
|
+
notify(event) {
|
|
319
|
+
if (event.type === "auth_url") ui.notify(`Open this URL to authorize: ${event.url}${event.instructions !== void 0 ? `\n${event.instructions}` : ""}`);
|
|
320
|
+
else if (event.type === "device_code") ui.notify(`Visit ${event.verificationUri} and enter code ${event.userCode}`);
|
|
321
|
+
else if (event.message !== void 0) ui.notify(event.message);
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function oauthConfigOf(providerConfig) {
|
|
326
|
+
const oauth = providerConfig?.oauth;
|
|
327
|
+
return typeof oauth === "object" && oauth !== null && typeof oauth.login === "function" ? oauth : void 0;
|
|
328
|
+
}
|
|
329
|
+
function oauthAdapterOf(oauthConfig) {
|
|
330
|
+
if (typeof oauthConfig.toAuth === "function") return oauthConfig;
|
|
331
|
+
return adaptOAuth(oauthConfig);
|
|
332
|
+
}
|
|
333
|
+
function providerSupportsOAuth(providerConfig) {
|
|
334
|
+
return oauthConfigOf(providerConfig) !== void 0;
|
|
335
|
+
}
|
|
336
|
+
async function loginPiProvider(options) {
|
|
337
|
+
const oauthConfig = oauthConfigOf(options.providerConfig);
|
|
338
|
+
if (oauthConfig === void 0) throw new Error(`${options.providerName ?? options.providerId} does not support oauth login`);
|
|
339
|
+
const credential = await oauthAdapterOf(oauthConfig).login(oauthInteraction(options.ui, options.signal));
|
|
340
|
+
await options.store.modify(options.providerId, async () => credential);
|
|
341
|
+
return credential;
|
|
342
|
+
}
|
|
343
|
+
async function resolveOAuthApiKey(options) {
|
|
344
|
+
const oauthConfig = oauthConfigOf(options.providerConfig);
|
|
345
|
+
if (oauthConfig === void 0) return void 0;
|
|
346
|
+
return (await resolveProviderAuth({
|
|
347
|
+
id: options.providerId,
|
|
348
|
+
name: options.providerName ?? options.providerId,
|
|
349
|
+
auth: { oauth: oauthAdapterOf(oauthConfig) }
|
|
350
|
+
}, options.store, { env: async () => void 0 }, options.signal !== void 0 ? { signal: options.signal } : void 0))?.auth?.apiKey;
|
|
351
|
+
}
|
|
352
|
+
async function storedOAuthCredential(store, providerId) {
|
|
353
|
+
const stored = await store.read(providerId, void 0);
|
|
354
|
+
return stored?.type === "oauth" ? stored : void 0;
|
|
355
|
+
}
|
|
356
|
+
//#endregion
|
|
357
|
+
export { storedOAuthCredential as a, resolveOAuthApiKey as i, loginPiProvider as n, providerSupportsOAuth as r, FileCredentialStore as t };
|
|
358
|
+
|
|
359
|
+
//# sourceMappingURL=oauth-bridge-BpPrppjR.mjs.map
|