@agimon-ai/po-core 0.0.0-alpha.115 → 0.0.0-alpha.117

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.
Files changed (35) hide show
  1. package/dist/contextProjection.cjs +20 -1
  2. package/dist/contextProjection.cjs.map +1 -1
  3. package/dist/contextProjection.d.mts +5 -0
  4. package/dist/contextProjection.d.mts.map +1 -1
  5. package/dist/contextProjection.mjs +20 -2
  6. package/dist/contextProjection.mjs.map +1 -1
  7. package/dist/headlessSessionHost.d.mts +2 -0
  8. package/dist/headlessSessionHost.d.mts.map +1 -1
  9. package/dist/history.cjs +1 -0
  10. package/dist/history.d.mts +2 -2
  11. package/dist/history.mjs +2 -2
  12. package/dist/src/services/edgeModelRuntime/index.cjs +16 -14
  13. package/dist/src/services/edgeModelRuntime/index.cjs.map +1 -1
  14. package/dist/src/services/edgeModelRuntime/index.d.mts.map +1 -1
  15. package/dist/src/services/edgeModelRuntime/index.mjs +16 -14
  16. package/dist/src/services/edgeModelRuntime/index.mjs.map +1 -1
  17. package/dist/src/services/providerAuth/index.cjs +1 -1
  18. package/dist/src/services/providerAuth/index.cjs.map +1 -1
  19. package/dist/src/services/providerAuth/index.mjs +1 -1
  20. package/dist/src/services/providerAuth/index.mjs.map +1 -1
  21. package/dist/src/services/sqliteSessionHistory/index.cjs +29 -3
  22. package/dist/src/services/sqliteSessionHistory/index.cjs.map +1 -1
  23. package/dist/src/services/sqliteSessionHistory/index.d.mts +7 -0
  24. package/dist/src/services/sqliteSessionHistory/index.d.mts.map +1 -1
  25. package/dist/src/services/sqliteSessionHistory/index.mjs +26 -1
  26. package/dist/src/services/sqliteSessionHistory/index.mjs.map +1 -1
  27. package/dist/src/services/sqliteTranscriptReader/index.cjs +12 -12
  28. package/dist/src/services/sqliteTranscriptReader/index.cjs.map +1 -1
  29. package/dist/src/services/sqliteTranscriptReader/index.mjs +2 -2
  30. package/dist/src/systems/main/adapters/headlessSessionHost.cjs +22 -14
  31. package/dist/src/systems/main/adapters/headlessSessionHost.cjs.map +1 -1
  32. package/dist/src/systems/main/adapters/headlessSessionHost.d.mts.map +1 -1
  33. package/dist/src/systems/main/adapters/headlessSessionHost.mjs +22 -14
  34. package/dist/src/systems/main/adapters/headlessSessionHost.mjs.map +1 -1
  35. package/package.json +4 -4
@@ -37,6 +37,23 @@ const SOURCE_ORDER = {
37
37
  plugin: 2,
38
38
  core: 3
39
39
  };
40
+ /** Reads only replayable selection fields, accepting legacy projections without a request marker. */
41
+ function readProjectedSelection(data) {
42
+ if (typeof data !== "object" || data === null || Array.isArray(data) || !("selection" in data)) return void 0;
43
+ const selection = data.selection;
44
+ if (typeof selection !== "object" || selection === null || Array.isArray(selection)) return void 0;
45
+ if (!("majorMode" in selection) || typeof selection.majorMode !== "string") return void 0;
46
+ if (!("domains" in selection) || !Array.isArray(selection.domains) || !selection.domains.every((domain) => typeof domain === "string")) return void 0;
47
+ const profile = "profile" in selection ? selection.profile : void 0;
48
+ if (profile !== void 0 && typeof profile !== "string") return void 0;
49
+ const requestedProfile = "requestedProfile" in selection ? selection.requestedProfile : void 0;
50
+ return {
51
+ majorMode: selection.majorMode,
52
+ domains: [...selection.domains],
53
+ profile,
54
+ ...typeof requestedProfile === "string" || requestedProfile === null ? { requestedProfile } : {}
55
+ };
56
+ }
40
57
  function skillSource(group) {
41
58
  if (group === "plugins") return "plugin";
42
59
  if (group === "extensions") return "extension";
@@ -107,7 +124,8 @@ function projectContext(input) {
107
124
  selection: {
108
125
  majorMode: input.majorMode,
109
126
  domains: [...input.domains],
110
- ...input.profile === void 0 ? {} : { profile: input.profile }
127
+ ...input.profile === void 0 ? {} : { profile: input.profile },
128
+ ...input.requestedProfile === void 0 ? {} : { requestedProfile: input.requestedProfile }
111
129
  },
112
130
  groups,
113
131
  totalTokens: groups.reduce((total, group) => total + group.tokens, 0),
@@ -123,5 +141,6 @@ exports.PO_CONFIG_ENTRY_TYPE = PO_CONFIG_ENTRY_TYPE;
123
141
  exports.PO_CONFIG_TRANSITION_ENTRY_TYPE = PO_CONFIG_TRANSITION_ENTRY_TYPE;
124
142
  exports.PO_CONTEXT_ENTRY_TYPE = PO_CONTEXT_ENTRY_TYPE;
125
143
  exports.projectContext = projectContext;
144
+ exports.readProjectedSelection = readProjectedSelection;
126
145
 
127
146
  //# sourceMappingURL=contextProjection.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"contextProjection.cjs","names":["tokensForTool"],"sources":["../src/services/contextProjection/index.ts"],"sourcesContent":["/** What admitted a package into the composition. */\nexport interface PackageAttribution {\n /** `major` for a layer package, `domain` for a plugin a domain carries. */\n kind: 'major' | 'domain';\n /** The major mode name, or the domain name. */\n mode: string;\n /** The layer the package was listed under; absent for a domain plugin. */\n layer?: string;\n}\n\nimport type { ContextPromptStage } from '../../types/contextApi';\nimport { type CountTokens, type ToolEntry, type ToolSource, tokensForTool } from '../toolInventory';\n\n/**\n * What the session is carrying, and what carrying it costs.\n *\n * Grouped by the mode that admitted each package rather than by where the tool\n * came from, because a mode is the thing a reader can switch off. The mechanism\n * (extension, MCP server, plugin) orders rows inside a group instead, so the\n * expensive thing is still easy to find.\n *\n * Schemas never travel. Only names, kinds, owners and integer counts do, which\n * keeps the payload small enough to republish whenever the composition changes.\n */\nexport const CONTEXT_PROJECTION_VERSION = 1;\n\n/** Journal entry the cockpit reads; mirrored by CONTEXT_ENTRY_TYPE in po-web. */\nexport const PO_CONTEXT_ENTRY_TYPE = 'po-context';\n\n/** Session selection and transition journal identities, not transport channels. */\nexport const PO_CONFIG_ENTRY_TYPE = 'po:config:v1';\nexport const PO_CONFIG_TRANSITION_ENTRY_TYPE = 'po:transition:v1';\n\n/** The tokenizer these counts came from, so a reader can judge them. */\nexport const CONTEXT_PROJECTION_ESTIMATOR = 'gpt-tokenizer';\n\nexport type ContextGroupKind = 'major' | 'minor' | 'domain' | 'shared' | 'core';\nexport type ContextItemSource = 'extension' | 'mcp' | 'plugin' | 'core';\n\n/** Everything the runtime could not attribute lands here rather than vanishing. */\nconst CORE_GROUP = 'core';\n/** Pi's own tools have no package; the runtime itself is the owner. */\nconst CORE_OWNER = 'pi';\nconst MCP_KEY_PREFIX = 'mcp:';\n\nconst KIND_ORDER: Record<ContextGroupKind, number> = { major: 0, minor: 1, domain: 2, shared: 3, core: 4 };\nconst SOURCE_ORDER: Record<ContextItemSource, number> = { extension: 0, mcp: 1, plugin: 2, core: 3 };\n\nexport interface ContextItemProjection {\n readonly name: string;\n readonly itemKind: 'tool' | 'skill';\n readonly source: ContextItemSource;\n /** The package, server, or plugin that registered it. */\n readonly owner: string;\n readonly tokens: number;\n readonly active: boolean;\n}\n\nexport interface ContextGroupProjection {\n readonly id: string;\n readonly label: string;\n readonly kind: ContextGroupKind;\n readonly items: readonly ContextItemProjection[];\n /** What the group costs now: active items only. */\n readonly tokens: number;\n /** What its gated items would add if switched on. */\n readonly inactiveTokens: number;\n}\n\n/**\n * What the assembled system prompt costs, and how complete the figure is.\n *\n * Kept out of `groups` on purpose. Every tool's prose and every skill's listing\n * already count inside the group subtotals, and those are slices of this same\n * text, so folding it into `totalTokens` would bill them twice.\n */\nexport interface ContextSystemPromptProjection {\n readonly tokens: number;\n readonly stage: ContextPromptStage;\n}\n\nexport interface ContextProjection {\n readonly version: typeof CONTEXT_PROJECTION_VERSION;\n readonly revision: number;\n /** Selection snapshot used to build this projection, replayable after reconnect. */\n readonly selection: {\n readonly majorMode: string;\n readonly domains: readonly string[];\n readonly profile?: string;\n };\n readonly groups: readonly ContextGroupProjection[];\n readonly totalTokens: number;\n readonly inactiveTokens: number;\n readonly systemPrompt?: ContextSystemPromptProjection;\n readonly estimator: typeof CONTEXT_PROJECTION_ESTIMATOR;\n}\n\nexport interface ContextConditionalAttribution {\n readonly kind: 'minor' | 'domain' | 'shared';\n readonly mode: string;\n readonly label?: string;\n}\n\nexport interface ContextToolInventory extends ToolEntry {\n /** A contribution-level gate is more specific than its package's major-mode owner. */\n readonly contextAttribution?: ContextConditionalAttribution;\n}\n\nexport interface ContextToolSource extends Omit<ToolSource, 'tools'> {\n readonly tools: readonly ContextToolInventory[];\n}\n\n/** Skill fields needed to project cost and serve detail. Virtual resources may not have a filesystem path. */\nexport interface ContextSkillInventory {\n readonly name: string;\n readonly description: string;\n readonly group: 'extensions' | 'help' | 'plugins' | 'default';\n readonly owner: string;\n readonly modelInvocable: boolean;\n readonly promptTokens?: number;\n readonly filePath?: string;\n /** The skill's instructions, when the host holds them; read only when a reader opens the row. */\n readonly body?: string;\n readonly contextAttribution?: ContextConditionalAttribution;\n}\n\nexport interface ContextProjectionInput {\n readonly revision: number;\n readonly majorMode: string;\n readonly profile?: string;\n /** Additional groups supplied by the composition owner, in display order. */\n readonly groups: readonly { readonly id: string; readonly label: string; readonly kind: ContextGroupKind }[];\n /** Domains the composition resolved under, whether or not they carried a plugin. */\n readonly domains: readonly string[];\n readonly sources: readonly ContextToolSource[];\n readonly skills: readonly ContextSkillInventory[];\n /**\n * Owner identifier to the mode that admitted it.\n *\n * Keys are package names for extensions and layer packages, and plugin or\n * server names for anything a domain carries. The caller owns this join\n * because only it knows how a given deployment resolved its plugins.\n */\n readonly attribution: Readonly<Record<string, PackageAttribution>>;\n readonly countTokens: CountTokens;\n /** Absent until the session has a prompt to report. */\n readonly systemPrompt?: ContextSystemPromptProjection;\n}\n\ninterface Bucket {\n kind: ContextGroupKind;\n label: string;\n items: ContextItemProjection[];\n}\n\nfunction skillSource(group: ContextSkillInventory['group']): ContextItemSource {\n if (group === 'plugins') return 'plugin';\n if (group === 'extensions') return 'extension';\n return 'core';\n}\n\nfunction byRow(left: ContextItemProjection, right: ContextItemProjection): number {\n return SOURCE_ORDER[left.source] - SOURCE_ORDER[right.source] || left.name.localeCompare(right.name);\n}\n\nexport function projectContext(input: ContextProjectionInput): ContextProjection {\n const buckets = new Map<string, Bucket>();\n\n // Declared before anything is placed, so a mode with nothing attributed to it\n // still appears. An empty group is a fact worth showing: it says the mode is\n // on and cheap, not that the runtime forgot about it.\n const declare = (id: string, label: string, kind: ContextGroupKind): void => {\n if (!buckets.has(id)) buckets.set(id, { kind, label, items: [] });\n };\n if (input.majorMode) declare(input.majorMode, input.majorMode, 'major');\n for (const group of input.groups) declare(group.id, group.label, group.kind);\n // A domain that contributed no plugin is still part of what the session is\n // running under, and a reader comparing the panel against the footer would\n // otherwise read its absence as the panel being out of date.\n for (const domain of input.domains) declare(domain, domain, 'domain');\n\n const place = (owner: string, item: ContextItemProjection, conditional?: ContextConditionalAttribution): void => {\n const attributed = conditional ?? input.attribution[owner];\n const id = attributed?.mode ?? CORE_GROUP;\n const kind = attributed?.kind ?? 'core';\n declare(id, attributed && 'label' in attributed ? (attributed.label ?? id) : id, kind);\n buckets.get(id)?.items.push(item);\n };\n\n for (const source of input.sources) {\n // An extension is owned by its package, an MCP tool by the server that\n // advertised it. `label` is display text like `scaffold · mcp`, so the\n // identifier comes from the key instead: an owner is joined on, not read.\n const owner =\n source.kind === 'extension'\n ? (source.packageName ?? source.key)\n : source.kind === 'mcp'\n ? source.key.slice(MCP_KEY_PREFIX.length)\n : CORE_OWNER;\n for (const tool of source.tools) {\n const cost = tokensForTool(tool, input.countTokens);\n const item: ContextItemProjection = {\n name: tool.name,\n itemKind: 'tool',\n source: source.kind === 'core' ? 'core' : source.kind,\n owner,\n tokens: cost.totalTokens,\n active: tool.active,\n };\n if (source.kind === 'core') {\n declare(CORE_GROUP, CORE_GROUP, 'core');\n buckets.get(CORE_GROUP)?.items.push(item);\n } else {\n place(owner, item, tool.contextAttribution);\n }\n }\n }\n\n for (const skill of input.skills) {\n place(\n skill.owner,\n {\n name: skill.name,\n itemKind: 'skill',\n source: skillSource(skill.group),\n owner: skill.owner,\n tokens: skill.promptTokens ?? 0,\n // A skill is always offered to the model; there is no inactive state for\n // it the way there is for a tool Pi has filtered out.\n active: true,\n },\n skill.contextAttribution,\n );\n }\n\n const groups: ContextGroupProjection[] = [...buckets.entries()]\n .map(([id, bucket]) => {\n const items = [...bucket.items].sort(byRow);\n // Only what is actually sent counts. Pi builds the prompt from its\n // active tools, so a gated one costs nothing until it is switched on,\n // and folding it into the total would overstate the bill by a third.\n return {\n id,\n label: bucket.label,\n kind: bucket.kind,\n items,\n tokens: items.reduce((total, item) => total + (item.active ? item.tokens : 0), 0),\n inactiveTokens: items.reduce((total, item) => total + (item.active ? 0 : item.tokens), 0),\n };\n })\n .sort((left, right) => KIND_ORDER[left.kind] - KIND_ORDER[right.kind] || left.label.localeCompare(right.label));\n\n return {\n version: CONTEXT_PROJECTION_VERSION,\n revision: input.revision,\n selection: {\n majorMode: input.majorMode,\n domains: [...input.domains],\n ...(input.profile === undefined ? {} : { profile: input.profile }),\n },\n groups,\n totalTokens: groups.reduce((total, group) => total + group.tokens, 0),\n inactiveTokens: groups.reduce((total, group) => total + group.inactiveTokens, 0),\n ...(input.systemPrompt === undefined ? {} : { systemPrompt: input.systemPrompt }),\n estimator: CONTEXT_PROJECTION_ESTIMATOR,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"contextProjection.cjs","names":["tokensForTool"],"sources":["../src/services/contextProjection/index.ts"],"sourcesContent":["/** What admitted a package into the composition. */\nexport interface PackageAttribution {\n /** `major` for a layer package, `domain` for a plugin a domain carries. */\n kind: 'major' | 'domain';\n /** The major mode name, or the domain name. */\n mode: string;\n /** The layer the package was listed under; absent for a domain plugin. */\n layer?: string;\n}\n\nimport type { ContextPromptStage } from '../../types/contextApi';\nimport { type CountTokens, type ToolEntry, type ToolSource, tokensForTool } from '../toolInventory';\n\n/**\n * What the session is carrying, and what carrying it costs.\n *\n * Grouped by the mode that admitted each package rather than by where the tool\n * came from, because a mode is the thing a reader can switch off. The mechanism\n * (extension, MCP server, plugin) orders rows inside a group instead, so the\n * expensive thing is still easy to find.\n *\n * Schemas never travel. Only names, kinds, owners and integer counts do, which\n * keeps the payload small enough to republish whenever the composition changes.\n */\nexport const CONTEXT_PROJECTION_VERSION = 1;\n\n/** Journal entry the cockpit reads; mirrored by CONTEXT_ENTRY_TYPE in po-web. */\nexport const PO_CONTEXT_ENTRY_TYPE = 'po-context';\n\n/** Session selection and transition journal identities, not transport channels. */\nexport const PO_CONFIG_ENTRY_TYPE = 'po:config:v1';\nexport const PO_CONFIG_TRANSITION_ENTRY_TYPE = 'po:transition:v1';\n\n/** The tokenizer these counts came from, so a reader can judge them. */\nexport const CONTEXT_PROJECTION_ESTIMATOR = 'gpt-tokenizer';\n\nexport type ContextGroupKind = 'major' | 'minor' | 'domain' | 'shared' | 'core';\nexport type ContextItemSource = 'extension' | 'mcp' | 'plugin' | 'core';\n\n/** Everything the runtime could not attribute lands here rather than vanishing. */\nconst CORE_GROUP = 'core';\n/** Pi's own tools have no package; the runtime itself is the owner. */\nconst CORE_OWNER = 'pi';\nconst MCP_KEY_PREFIX = 'mcp:';\n\nconst KIND_ORDER: Record<ContextGroupKind, number> = { major: 0, minor: 1, domain: 2, shared: 3, core: 4 };\nconst SOURCE_ORDER: Record<ContextItemSource, number> = { extension: 0, mcp: 1, plugin: 2, core: 3 };\n\nexport interface ContextItemProjection {\n readonly name: string;\n readonly itemKind: 'tool' | 'skill';\n readonly source: ContextItemSource;\n /** The package, server, or plugin that registered it. */\n readonly owner: string;\n readonly tokens: number;\n readonly active: boolean;\n}\n\nexport interface ContextGroupProjection {\n readonly id: string;\n readonly label: string;\n readonly kind: ContextGroupKind;\n readonly items: readonly ContextItemProjection[];\n /** What the group costs now: active items only. */\n readonly tokens: number;\n /** What its gated items would add if switched on. */\n readonly inactiveTokens: number;\n}\n\n/**\n * What the assembled system prompt costs, and how complete the figure is.\n *\n * Kept out of `groups` on purpose. Every tool's prose and every skill's listing\n * already count inside the group subtotals, and those are slices of this same\n * text, so folding it into `totalTokens` would bill them twice.\n */\nexport interface ContextSystemPromptProjection {\n readonly tokens: number;\n readonly stage: ContextPromptStage;\n}\n\nexport interface ContextProjection {\n readonly version: typeof CONTEXT_PROJECTION_VERSION;\n readonly revision: number;\n /** Selection snapshot used to build this projection, replayable after reconnect. */\n readonly selection: {\n readonly majorMode: string;\n readonly domains: readonly string[];\n readonly profile?: string;\n /** Requested profile when none is active; null records an explicit None pick. Absent on legacy records. */\n readonly requestedProfile?: string | null;\n };\n readonly groups: readonly ContextGroupProjection[];\n readonly totalTokens: number;\n readonly inactiveTokens: number;\n readonly systemPrompt?: ContextSystemPromptProjection;\n readonly estimator: typeof CONTEXT_PROJECTION_ESTIMATOR;\n}\n\nexport interface ContextConditionalAttribution {\n readonly kind: 'minor' | 'domain' | 'shared';\n readonly mode: string;\n readonly label?: string;\n}\n\nexport interface ContextToolInventory extends ToolEntry {\n /** A contribution-level gate is more specific than its package's major-mode owner. */\n readonly contextAttribution?: ContextConditionalAttribution;\n}\n\nexport interface ContextToolSource extends Omit<ToolSource, 'tools'> {\n readonly tools: readonly ContextToolInventory[];\n}\n\n/** Skill fields needed to project cost and serve detail. Virtual resources may not have a filesystem path. */\nexport interface ContextSkillInventory {\n readonly name: string;\n readonly description: string;\n readonly group: 'extensions' | 'help' | 'plugins' | 'default';\n readonly owner: string;\n readonly modelInvocable: boolean;\n readonly promptTokens?: number;\n readonly filePath?: string;\n /** The skill's instructions, when the host holds them; read only when a reader opens the row. */\n readonly body?: string;\n readonly contextAttribution?: ContextConditionalAttribution;\n}\n\nexport interface ContextProjectionInput {\n readonly revision: number;\n readonly majorMode: string;\n readonly profile?: string;\n readonly requestedProfile?: string | null;\n /** Additional groups supplied by the composition owner, in display order. */\n readonly groups: readonly { readonly id: string; readonly label: string; readonly kind: ContextGroupKind }[];\n /** Domains the composition resolved under, whether or not they carried a plugin. */\n readonly domains: readonly string[];\n readonly sources: readonly ContextToolSource[];\n readonly skills: readonly ContextSkillInventory[];\n /**\n * Owner identifier to the mode that admitted it.\n *\n * Keys are package names for extensions and layer packages, and plugin or\n * server names for anything a domain carries. The caller owns this join\n * because only it knows how a given deployment resolved its plugins.\n */\n readonly attribution: Readonly<Record<string, PackageAttribution>>;\n readonly countTokens: CountTokens;\n /** Absent until the session has a prompt to report. */\n readonly systemPrompt?: ContextSystemPromptProjection;\n}\n\n/** Reads only replayable selection fields, accepting legacy projections without a request marker. */\nexport function readProjectedSelection(data: unknown): ContextProjection['selection'] | undefined {\n if (typeof data !== 'object' || data === null || Array.isArray(data) || !('selection' in data)) return undefined;\n const selection = data.selection;\n if (typeof selection !== 'object' || selection === null || Array.isArray(selection)) return undefined;\n if (!('majorMode' in selection) || typeof selection.majorMode !== 'string') return undefined;\n if (\n !('domains' in selection) ||\n !Array.isArray(selection.domains) ||\n !selection.domains.every((domain) => typeof domain === 'string')\n )\n return undefined;\n const profile = 'profile' in selection ? selection.profile : undefined;\n if (profile !== undefined && typeof profile !== 'string') return undefined;\n const requestedProfile = 'requestedProfile' in selection ? selection.requestedProfile : undefined;\n return {\n majorMode: selection.majorMode,\n domains: [...selection.domains],\n profile,\n ...(typeof requestedProfile === 'string' || requestedProfile === null ? { requestedProfile } : {}),\n };\n}\n\ninterface Bucket {\n kind: ContextGroupKind;\n label: string;\n items: ContextItemProjection[];\n}\n\nfunction skillSource(group: ContextSkillInventory['group']): ContextItemSource {\n if (group === 'plugins') return 'plugin';\n if (group === 'extensions') return 'extension';\n return 'core';\n}\n\nfunction byRow(left: ContextItemProjection, right: ContextItemProjection): number {\n return SOURCE_ORDER[left.source] - SOURCE_ORDER[right.source] || left.name.localeCompare(right.name);\n}\n\nexport function projectContext(input: ContextProjectionInput): ContextProjection {\n const buckets = new Map<string, Bucket>();\n\n // Declared before anything is placed, so a mode with nothing attributed to it\n // still appears. An empty group is a fact worth showing: it says the mode is\n // on and cheap, not that the runtime forgot about it.\n const declare = (id: string, label: string, kind: ContextGroupKind): void => {\n if (!buckets.has(id)) buckets.set(id, { kind, label, items: [] });\n };\n if (input.majorMode) declare(input.majorMode, input.majorMode, 'major');\n for (const group of input.groups) declare(group.id, group.label, group.kind);\n // A domain that contributed no plugin is still part of what the session is\n // running under, and a reader comparing the panel against the footer would\n // otherwise read its absence as the panel being out of date.\n for (const domain of input.domains) declare(domain, domain, 'domain');\n\n const place = (owner: string, item: ContextItemProjection, conditional?: ContextConditionalAttribution): void => {\n const attributed = conditional ?? input.attribution[owner];\n const id = attributed?.mode ?? CORE_GROUP;\n const kind = attributed?.kind ?? 'core';\n declare(id, attributed && 'label' in attributed ? (attributed.label ?? id) : id, kind);\n buckets.get(id)?.items.push(item);\n };\n\n for (const source of input.sources) {\n // An extension is owned by its package, an MCP tool by the server that\n // advertised it. `label` is display text like `scaffold · mcp`, so the\n // identifier comes from the key instead: an owner is joined on, not read.\n const owner =\n source.kind === 'extension'\n ? (source.packageName ?? source.key)\n : source.kind === 'mcp'\n ? source.key.slice(MCP_KEY_PREFIX.length)\n : CORE_OWNER;\n for (const tool of source.tools) {\n const cost = tokensForTool(tool, input.countTokens);\n const item: ContextItemProjection = {\n name: tool.name,\n itemKind: 'tool',\n source: source.kind === 'core' ? 'core' : source.kind,\n owner,\n tokens: cost.totalTokens,\n active: tool.active,\n };\n if (source.kind === 'core') {\n declare(CORE_GROUP, CORE_GROUP, 'core');\n buckets.get(CORE_GROUP)?.items.push(item);\n } else {\n place(owner, item, tool.contextAttribution);\n }\n }\n }\n\n for (const skill of input.skills) {\n place(\n skill.owner,\n {\n name: skill.name,\n itemKind: 'skill',\n source: skillSource(skill.group),\n owner: skill.owner,\n tokens: skill.promptTokens ?? 0,\n // A skill is always offered to the model; there is no inactive state for\n // it the way there is for a tool Pi has filtered out.\n active: true,\n },\n skill.contextAttribution,\n );\n }\n\n const groups: ContextGroupProjection[] = [...buckets.entries()]\n .map(([id, bucket]) => {\n const items = [...bucket.items].sort(byRow);\n // Only what is actually sent counts. Pi builds the prompt from its\n // active tools, so a gated one costs nothing until it is switched on,\n // and folding it into the total would overstate the bill by a third.\n return {\n id,\n label: bucket.label,\n kind: bucket.kind,\n items,\n tokens: items.reduce((total, item) => total + (item.active ? item.tokens : 0), 0),\n inactiveTokens: items.reduce((total, item) => total + (item.active ? 0 : item.tokens), 0),\n };\n })\n .sort((left, right) => KIND_ORDER[left.kind] - KIND_ORDER[right.kind] || left.label.localeCompare(right.label));\n\n return {\n version: CONTEXT_PROJECTION_VERSION,\n revision: input.revision,\n selection: {\n majorMode: input.majorMode,\n domains: [...input.domains],\n ...(input.profile === undefined ? {} : { profile: input.profile }),\n ...(input.requestedProfile === undefined ? {} : { requestedProfile: input.requestedProfile }),\n },\n groups,\n totalTokens: groups.reduce((total, group) => total + group.tokens, 0),\n inactiveTokens: groups.reduce((total, group) => total + group.inactiveTokens, 0),\n ...(input.systemPrompt === undefined ? {} : { systemPrompt: input.systemPrompt }),\n estimator: CONTEXT_PROJECTION_ESTIMATOR,\n };\n}\n"],"mappings":"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"}
@@ -70,6 +70,8 @@ export interface ContextProjection {
70
70
  readonly majorMode: string;
71
71
  readonly domains: readonly string[];
72
72
  readonly profile?: string;
73
+ /** Requested profile when none is active; null records an explicit None pick. Absent on legacy records. */
74
+ readonly requestedProfile?: string | null;
73
75
  };
74
76
  readonly groups: readonly ContextGroupProjection[];
75
77
  readonly totalTokens: number;
@@ -106,6 +108,7 @@ export interface ContextProjectionInput {
106
108
  readonly revision: number;
107
109
  readonly majorMode: string;
108
110
  readonly profile?: string;
111
+ readonly requestedProfile?: string | null;
109
112
  /** Additional groups supplied by the composition owner, in display order. */
110
113
  readonly groups: readonly {
111
114
  readonly id: string;
@@ -128,6 +131,8 @@ export interface ContextProjectionInput {
128
131
  /** Absent until the session has a prompt to report. */
129
132
  readonly systemPrompt?: ContextSystemPromptProjection;
130
133
  }
134
+ /** Reads only replayable selection fields, accepting legacy projections without a request marker. */
135
+ export declare function readProjectedSelection(data: unknown): ContextProjection['selection'] | undefined;
131
136
  export declare function projectContext(input: ContextProjectionInput): ContextProjection;
132
137
  //#endregion
133
138
  //# sourceMappingURL=contextProjection.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"contextProjection.d.mts","names":[],"sources":["../src/services/contextProjection/index.ts"],"mappings":";;;;;iBACiB;;EAEf;;EAEA;;EAEA;;;;;;;;;;;;;qBAiBW;;qBAGA;;qBAGA;qBACA;;qBAGA;YAED;YACA;iBAWK;WACN;WACA;WACA,QAAQ;;WAER;WACA;WACA;;iBAGM;WACN;WACA;WACA,MAAM;WACN,gBAAgB;;WAEhB;;WAEA;;;;;;;;;iBAUM;WACN;WACA,OAAO;;iBAGD;WACN,gBAAgB;WAChB;;WAEA;aACE;aACA;aACA;;WAEF,iBAAiB;WACjB;WACA;WACA,eAAe;WACf,kBAAkB;;iBAGZ;WACN;WACA;WACA;;iBAGM,6BAA6B;;WAEnC,qBAAqB;;iBAGf,0BAA0B,KAAK;WACrC,gBAAgB;;;iBAIV;WACN;WACA;WACA;WACA;WACA;WACA;WACA;;WAEA;WACA,qBAAqB;;iBAGf;WACN;WACA;WACA;;WAEA;aAA4B;aAAqB;aAAwB,MAAM;;;WAE/E;WACA,kBAAkB;WAClB,iBAAiB;;;;;;;;WAQjB,aAAa,SAAS,eAAe;WACrC,aAAa;;WAEb,eAAe;;wBAmBV,eAAe,OAAO,yBAAyB"}
1
+ {"version":3,"file":"contextProjection.d.mts","names":[],"sources":["../src/services/contextProjection/index.ts"],"mappings":";;;;;iBACiB;;EAEf;;EAEA;;EAEA;;;;;;;;;;;;;qBAiBW;;qBAGA;;qBAGA;qBACA;;qBAGA;YAED;YACA;iBAWK;WACN;WACA;WACA,QAAQ;;WAER;WACA;WACA;;iBAGM;WACN;WACA;WACA,MAAM;WACN,gBAAgB;;WAEhB;;WAEA;;;;;;;;;iBAUM;WACN;WACA,OAAO;;iBAGD;WACN,gBAAgB;WAChB;;WAEA;aACE;aACA;aACA;;aAEA;;WAEF,iBAAiB;WACjB;WACA;WACA,eAAe;WACf,kBAAkB;;iBAGZ;WACN;WACA;WACA;;iBAGM,6BAA6B;;WAEnC,qBAAqB;;iBAGf,0BAA0B,KAAK;WACrC,gBAAgB;;;iBAIV;WACN;WACA;WACA;WACA;WACA;WACA;WACA;;WAEA;WACA,qBAAqB;;iBAGf;WACN;WACA;WACA;WACA;;WAEA;aAA4B;aAAqB;aAAwB,MAAM;;;WAE/E;WACA,kBAAkB;WAClB,iBAAiB;;;;;;;;WAQjB,aAAa,SAAS,eAAe;WACrC,aAAa;;WAEb,eAAe;;;wBAIV,uBAAuB,gBAAgB;wBAsCvC,eAAe,OAAO,yBAAyB"}
@@ -36,6 +36,23 @@ const SOURCE_ORDER = {
36
36
  plugin: 2,
37
37
  core: 3
38
38
  };
39
+ /** Reads only replayable selection fields, accepting legacy projections without a request marker. */
40
+ function readProjectedSelection(data) {
41
+ if (typeof data !== "object" || data === null || Array.isArray(data) || !("selection" in data)) return void 0;
42
+ const selection = data.selection;
43
+ if (typeof selection !== "object" || selection === null || Array.isArray(selection)) return void 0;
44
+ if (!("majorMode" in selection) || typeof selection.majorMode !== "string") return void 0;
45
+ if (!("domains" in selection) || !Array.isArray(selection.domains) || !selection.domains.every((domain) => typeof domain === "string")) return void 0;
46
+ const profile = "profile" in selection ? selection.profile : void 0;
47
+ if (profile !== void 0 && typeof profile !== "string") return void 0;
48
+ const requestedProfile = "requestedProfile" in selection ? selection.requestedProfile : void 0;
49
+ return {
50
+ majorMode: selection.majorMode,
51
+ domains: [...selection.domains],
52
+ profile,
53
+ ...typeof requestedProfile === "string" || requestedProfile === null ? { requestedProfile } : {}
54
+ };
55
+ }
39
56
  function skillSource(group) {
40
57
  if (group === "plugins") return "plugin";
41
58
  if (group === "extensions") return "extension";
@@ -106,7 +123,8 @@ function projectContext(input) {
106
123
  selection: {
107
124
  majorMode: input.majorMode,
108
125
  domains: [...input.domains],
109
- ...input.profile === void 0 ? {} : { profile: input.profile }
126
+ ...input.profile === void 0 ? {} : { profile: input.profile },
127
+ ...input.requestedProfile === void 0 ? {} : { requestedProfile: input.requestedProfile }
110
128
  },
111
129
  groups,
112
130
  totalTokens: groups.reduce((total, group) => total + group.tokens, 0),
@@ -116,6 +134,6 @@ function projectContext(input) {
116
134
  };
117
135
  }
118
136
  //#endregion
119
- export { CONTEXT_PROJECTION_ESTIMATOR, CONTEXT_PROJECTION_VERSION, PO_CONFIG_ENTRY_TYPE, PO_CONFIG_TRANSITION_ENTRY_TYPE, PO_CONTEXT_ENTRY_TYPE, projectContext };
137
+ export { CONTEXT_PROJECTION_ESTIMATOR, CONTEXT_PROJECTION_VERSION, PO_CONFIG_ENTRY_TYPE, PO_CONFIG_TRANSITION_ENTRY_TYPE, PO_CONTEXT_ENTRY_TYPE, projectContext, readProjectedSelection };
120
138
 
121
139
  //# sourceMappingURL=contextProjection.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"contextProjection.mjs","names":[],"sources":["../src/services/contextProjection/index.ts"],"sourcesContent":["/** What admitted a package into the composition. */\nexport interface PackageAttribution {\n /** `major` for a layer package, `domain` for a plugin a domain carries. */\n kind: 'major' | 'domain';\n /** The major mode name, or the domain name. */\n mode: string;\n /** The layer the package was listed under; absent for a domain plugin. */\n layer?: string;\n}\n\nimport type { ContextPromptStage } from '../../types/contextApi';\nimport { type CountTokens, type ToolEntry, type ToolSource, tokensForTool } from '../toolInventory';\n\n/**\n * What the session is carrying, and what carrying it costs.\n *\n * Grouped by the mode that admitted each package rather than by where the tool\n * came from, because a mode is the thing a reader can switch off. The mechanism\n * (extension, MCP server, plugin) orders rows inside a group instead, so the\n * expensive thing is still easy to find.\n *\n * Schemas never travel. Only names, kinds, owners and integer counts do, which\n * keeps the payload small enough to republish whenever the composition changes.\n */\nexport const CONTEXT_PROJECTION_VERSION = 1;\n\n/** Journal entry the cockpit reads; mirrored by CONTEXT_ENTRY_TYPE in po-web. */\nexport const PO_CONTEXT_ENTRY_TYPE = 'po-context';\n\n/** Session selection and transition journal identities, not transport channels. */\nexport const PO_CONFIG_ENTRY_TYPE = 'po:config:v1';\nexport const PO_CONFIG_TRANSITION_ENTRY_TYPE = 'po:transition:v1';\n\n/** The tokenizer these counts came from, so a reader can judge them. */\nexport const CONTEXT_PROJECTION_ESTIMATOR = 'gpt-tokenizer';\n\nexport type ContextGroupKind = 'major' | 'minor' | 'domain' | 'shared' | 'core';\nexport type ContextItemSource = 'extension' | 'mcp' | 'plugin' | 'core';\n\n/** Everything the runtime could not attribute lands here rather than vanishing. */\nconst CORE_GROUP = 'core';\n/** Pi's own tools have no package; the runtime itself is the owner. */\nconst CORE_OWNER = 'pi';\nconst MCP_KEY_PREFIX = 'mcp:';\n\nconst KIND_ORDER: Record<ContextGroupKind, number> = { major: 0, minor: 1, domain: 2, shared: 3, core: 4 };\nconst SOURCE_ORDER: Record<ContextItemSource, number> = { extension: 0, mcp: 1, plugin: 2, core: 3 };\n\nexport interface ContextItemProjection {\n readonly name: string;\n readonly itemKind: 'tool' | 'skill';\n readonly source: ContextItemSource;\n /** The package, server, or plugin that registered it. */\n readonly owner: string;\n readonly tokens: number;\n readonly active: boolean;\n}\n\nexport interface ContextGroupProjection {\n readonly id: string;\n readonly label: string;\n readonly kind: ContextGroupKind;\n readonly items: readonly ContextItemProjection[];\n /** What the group costs now: active items only. */\n readonly tokens: number;\n /** What its gated items would add if switched on. */\n readonly inactiveTokens: number;\n}\n\n/**\n * What the assembled system prompt costs, and how complete the figure is.\n *\n * Kept out of `groups` on purpose. Every tool's prose and every skill's listing\n * already count inside the group subtotals, and those are slices of this same\n * text, so folding it into `totalTokens` would bill them twice.\n */\nexport interface ContextSystemPromptProjection {\n readonly tokens: number;\n readonly stage: ContextPromptStage;\n}\n\nexport interface ContextProjection {\n readonly version: typeof CONTEXT_PROJECTION_VERSION;\n readonly revision: number;\n /** Selection snapshot used to build this projection, replayable after reconnect. */\n readonly selection: {\n readonly majorMode: string;\n readonly domains: readonly string[];\n readonly profile?: string;\n };\n readonly groups: readonly ContextGroupProjection[];\n readonly totalTokens: number;\n readonly inactiveTokens: number;\n readonly systemPrompt?: ContextSystemPromptProjection;\n readonly estimator: typeof CONTEXT_PROJECTION_ESTIMATOR;\n}\n\nexport interface ContextConditionalAttribution {\n readonly kind: 'minor' | 'domain' | 'shared';\n readonly mode: string;\n readonly label?: string;\n}\n\nexport interface ContextToolInventory extends ToolEntry {\n /** A contribution-level gate is more specific than its package's major-mode owner. */\n readonly contextAttribution?: ContextConditionalAttribution;\n}\n\nexport interface ContextToolSource extends Omit<ToolSource, 'tools'> {\n readonly tools: readonly ContextToolInventory[];\n}\n\n/** Skill fields needed to project cost and serve detail. Virtual resources may not have a filesystem path. */\nexport interface ContextSkillInventory {\n readonly name: string;\n readonly description: string;\n readonly group: 'extensions' | 'help' | 'plugins' | 'default';\n readonly owner: string;\n readonly modelInvocable: boolean;\n readonly promptTokens?: number;\n readonly filePath?: string;\n /** The skill's instructions, when the host holds them; read only when a reader opens the row. */\n readonly body?: string;\n readonly contextAttribution?: ContextConditionalAttribution;\n}\n\nexport interface ContextProjectionInput {\n readonly revision: number;\n readonly majorMode: string;\n readonly profile?: string;\n /** Additional groups supplied by the composition owner, in display order. */\n readonly groups: readonly { readonly id: string; readonly label: string; readonly kind: ContextGroupKind }[];\n /** Domains the composition resolved under, whether or not they carried a plugin. */\n readonly domains: readonly string[];\n readonly sources: readonly ContextToolSource[];\n readonly skills: readonly ContextSkillInventory[];\n /**\n * Owner identifier to the mode that admitted it.\n *\n * Keys are package names for extensions and layer packages, and plugin or\n * server names for anything a domain carries. The caller owns this join\n * because only it knows how a given deployment resolved its plugins.\n */\n readonly attribution: Readonly<Record<string, PackageAttribution>>;\n readonly countTokens: CountTokens;\n /** Absent until the session has a prompt to report. */\n readonly systemPrompt?: ContextSystemPromptProjection;\n}\n\ninterface Bucket {\n kind: ContextGroupKind;\n label: string;\n items: ContextItemProjection[];\n}\n\nfunction skillSource(group: ContextSkillInventory['group']): ContextItemSource {\n if (group === 'plugins') return 'plugin';\n if (group === 'extensions') return 'extension';\n return 'core';\n}\n\nfunction byRow(left: ContextItemProjection, right: ContextItemProjection): number {\n return SOURCE_ORDER[left.source] - SOURCE_ORDER[right.source] || left.name.localeCompare(right.name);\n}\n\nexport function projectContext(input: ContextProjectionInput): ContextProjection {\n const buckets = new Map<string, Bucket>();\n\n // Declared before anything is placed, so a mode with nothing attributed to it\n // still appears. An empty group is a fact worth showing: it says the mode is\n // on and cheap, not that the runtime forgot about it.\n const declare = (id: string, label: string, kind: ContextGroupKind): void => {\n if (!buckets.has(id)) buckets.set(id, { kind, label, items: [] });\n };\n if (input.majorMode) declare(input.majorMode, input.majorMode, 'major');\n for (const group of input.groups) declare(group.id, group.label, group.kind);\n // A domain that contributed no plugin is still part of what the session is\n // running under, and a reader comparing the panel against the footer would\n // otherwise read its absence as the panel being out of date.\n for (const domain of input.domains) declare(domain, domain, 'domain');\n\n const place = (owner: string, item: ContextItemProjection, conditional?: ContextConditionalAttribution): void => {\n const attributed = conditional ?? input.attribution[owner];\n const id = attributed?.mode ?? CORE_GROUP;\n const kind = attributed?.kind ?? 'core';\n declare(id, attributed && 'label' in attributed ? (attributed.label ?? id) : id, kind);\n buckets.get(id)?.items.push(item);\n };\n\n for (const source of input.sources) {\n // An extension is owned by its package, an MCP tool by the server that\n // advertised it. `label` is display text like `scaffold · mcp`, so the\n // identifier comes from the key instead: an owner is joined on, not read.\n const owner =\n source.kind === 'extension'\n ? (source.packageName ?? source.key)\n : source.kind === 'mcp'\n ? source.key.slice(MCP_KEY_PREFIX.length)\n : CORE_OWNER;\n for (const tool of source.tools) {\n const cost = tokensForTool(tool, input.countTokens);\n const item: ContextItemProjection = {\n name: tool.name,\n itemKind: 'tool',\n source: source.kind === 'core' ? 'core' : source.kind,\n owner,\n tokens: cost.totalTokens,\n active: tool.active,\n };\n if (source.kind === 'core') {\n declare(CORE_GROUP, CORE_GROUP, 'core');\n buckets.get(CORE_GROUP)?.items.push(item);\n } else {\n place(owner, item, tool.contextAttribution);\n }\n }\n }\n\n for (const skill of input.skills) {\n place(\n skill.owner,\n {\n name: skill.name,\n itemKind: 'skill',\n source: skillSource(skill.group),\n owner: skill.owner,\n tokens: skill.promptTokens ?? 0,\n // A skill is always offered to the model; there is no inactive state for\n // it the way there is for a tool Pi has filtered out.\n active: true,\n },\n skill.contextAttribution,\n );\n }\n\n const groups: ContextGroupProjection[] = [...buckets.entries()]\n .map(([id, bucket]) => {\n const items = [...bucket.items].sort(byRow);\n // Only what is actually sent counts. Pi builds the prompt from its\n // active tools, so a gated one costs nothing until it is switched on,\n // and folding it into the total would overstate the bill by a third.\n return {\n id,\n label: bucket.label,\n kind: bucket.kind,\n items,\n tokens: items.reduce((total, item) => total + (item.active ? item.tokens : 0), 0),\n inactiveTokens: items.reduce((total, item) => total + (item.active ? 0 : item.tokens), 0),\n };\n })\n .sort((left, right) => KIND_ORDER[left.kind] - KIND_ORDER[right.kind] || left.label.localeCompare(right.label));\n\n return {\n version: CONTEXT_PROJECTION_VERSION,\n revision: input.revision,\n selection: {\n majorMode: input.majorMode,\n domains: [...input.domains],\n ...(input.profile === undefined ? {} : { profile: input.profile }),\n },\n groups,\n totalTokens: groups.reduce((total, group) => total + group.tokens, 0),\n inactiveTokens: groups.reduce((total, group) => total + group.inactiveTokens, 0),\n ...(input.systemPrompt === undefined ? {} : { systemPrompt: input.systemPrompt }),\n estimator: CONTEXT_PROJECTION_ESTIMATOR,\n };\n}\n"],"mappings":";;;;;;;;;;;;;AAwBA,MAAa,6BAA6B;;AAG1C,MAAa,wBAAwB;;AAGrC,MAAa,uBAAuB;AACpC,MAAa,kCAAkC;;AAG/C,MAAa,+BAA+B;;AAM5C,MAAM,aAAa;;AAEnB,MAAM,aAAa;AAGnB,MAAM,aAA+C;CAAE,OAAO;CAAG,OAAO;CAAG,QAAQ;CAAG,QAAQ;CAAG,MAAM;AAAE;AACzG,MAAM,eAAkD;CAAE,WAAW;CAAG,KAAK;CAAG,QAAQ;CAAG,MAAM;AAAE;AA6GnG,SAAS,YAAY,OAA0D;CAC7E,IAAI,UAAU,WAAW,OAAO;CAChC,IAAI,UAAU,cAAc,OAAO;CACnC,OAAO;AACT;AAEA,SAAS,MAAM,MAA6B,OAAsC;CAChF,OAAO,aAAa,KAAK,UAAU,aAAa,MAAM,WAAW,KAAK,KAAK,cAAc,MAAM,IAAI;AACrG;AAEA,SAAgB,eAAe,OAAkD;CAC/E,MAAM,0BAAU,IAAI,IAAoB;CAKxC,MAAM,WAAW,IAAY,OAAe,SAAiC;EAC3E,IAAI,CAAC,QAAQ,IAAI,EAAE,GAAG,QAAQ,IAAI,IAAI;GAAE;GAAM;GAAO,OAAO,CAAC;EAAE,CAAC;CAClE;CACA,IAAI,MAAM,WAAW,QAAQ,MAAM,WAAW,MAAM,WAAW,OAAO;CACtE,KAAK,MAAM,SAAS,MAAM,QAAQ,QAAQ,MAAM,IAAI,MAAM,OAAO,MAAM,IAAI;CAI3E,KAAK,MAAM,UAAU,MAAM,SAAS,QAAQ,QAAQ,QAAQ,QAAQ;CAEpE,MAAM,SAAS,OAAe,MAA6B,gBAAsD;EAC/G,MAAM,aAAa,eAAe,MAAM,YAAY;EACpD,MAAM,KAAK,YAAY,QAAQ;EAC/B,MAAM,OAAO,YAAY,QAAQ;EACjC,QAAQ,IAAI,cAAc,WAAW,aAAc,WAAW,SAAS,KAAM,IAAI,IAAI;EACrF,QAAQ,IAAI,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI;CAClC;CAEA,KAAK,MAAM,UAAU,MAAM,SAAS;EAIlC,MAAM,QACJ,OAAO,SAAS,cACX,OAAO,eAAe,OAAO,MAC9B,OAAO,SAAS,QACd,OAAO,IAAI,MAAM,CAAqB,IACtC;EACR,KAAK,MAAM,QAAQ,OAAO,OAAO;GAC/B,MAAM,OAAO,cAAc,MAAM,MAAM,WAAW;GAClD,MAAM,OAA8B;IAClC,MAAM,KAAK;IACX,UAAU;IACV,QAAQ,OAAO,SAAS,SAAS,SAAS,OAAO;IACjD;IACA,QAAQ,KAAK;IACb,QAAQ,KAAK;GACf;GACA,IAAI,OAAO,SAAS,QAAQ;IAC1B,QAAQ,YAAY,YAAY,MAAM;IACtC,QAAQ,IAAI,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI;GAC1C,OACE,MAAM,OAAO,MAAM,KAAK,kBAAkB;EAE9C;CACF;CAEA,KAAK,MAAM,SAAS,MAAM,QACxB,MACE,MAAM,OACN;EACE,MAAM,MAAM;EACZ,UAAU;EACV,QAAQ,YAAY,MAAM,KAAK;EAC/B,OAAO,MAAM;EACb,QAAQ,MAAM,gBAAgB;EAG9B,QAAQ;CACV,GACA,MAAM,kBACR;CAGF,MAAM,SAAmC,CAAC,GAAG,QAAQ,QAAQ,CAAC,CAAC,CAC5D,KAAK,CAAC,IAAI,YAAY;EACrB,MAAM,QAAQ,CAAC,GAAG,OAAO,KAAK,CAAC,CAAC,KAAK,KAAK;EAI1C,OAAO;GACL;GACA,OAAO,OAAO;GACd,MAAM,OAAO;GACb;GACA,QAAQ,MAAM,QAAQ,OAAO,SAAS,SAAS,KAAK,SAAS,KAAK,SAAS,IAAI,CAAC;GAChF,gBAAgB,MAAM,QAAQ,OAAO,SAAS,SAAS,KAAK,SAAS,IAAI,KAAK,SAAS,CAAC;EAC1F;CACF,CAAC,CAAC,CACD,MAAM,MAAM,UAAU,WAAW,KAAK,QAAQ,WAAW,MAAM,SAAS,KAAK,MAAM,cAAc,MAAM,KAAK,CAAC;CAEhH,OAAO;EACL,SAAA;EACA,UAAU,MAAM;EAChB,WAAW;GACT,WAAW,MAAM;GACjB,SAAS,CAAC,GAAG,MAAM,OAAO;GAC1B,GAAI,MAAM,YAAY,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS,MAAM,QAAQ;EAClE;EACA;EACA,aAAa,OAAO,QAAQ,OAAO,UAAU,QAAQ,MAAM,QAAQ,CAAC;EACpE,gBAAgB,OAAO,QAAQ,OAAO,UAAU,QAAQ,MAAM,gBAAgB,CAAC;EAC/E,GAAI,MAAM,iBAAiB,KAAA,IAAY,CAAC,IAAI,EAAE,cAAc,MAAM,aAAa;EAC/E,WAAW;CACb;AACF"}
1
+ {"version":3,"file":"contextProjection.mjs","names":[],"sources":["../src/services/contextProjection/index.ts"],"sourcesContent":["/** What admitted a package into the composition. */\nexport interface PackageAttribution {\n /** `major` for a layer package, `domain` for a plugin a domain carries. */\n kind: 'major' | 'domain';\n /** The major mode name, or the domain name. */\n mode: string;\n /** The layer the package was listed under; absent for a domain plugin. */\n layer?: string;\n}\n\nimport type { ContextPromptStage } from '../../types/contextApi';\nimport { type CountTokens, type ToolEntry, type ToolSource, tokensForTool } from '../toolInventory';\n\n/**\n * What the session is carrying, and what carrying it costs.\n *\n * Grouped by the mode that admitted each package rather than by where the tool\n * came from, because a mode is the thing a reader can switch off. The mechanism\n * (extension, MCP server, plugin) orders rows inside a group instead, so the\n * expensive thing is still easy to find.\n *\n * Schemas never travel. Only names, kinds, owners and integer counts do, which\n * keeps the payload small enough to republish whenever the composition changes.\n */\nexport const CONTEXT_PROJECTION_VERSION = 1;\n\n/** Journal entry the cockpit reads; mirrored by CONTEXT_ENTRY_TYPE in po-web. */\nexport const PO_CONTEXT_ENTRY_TYPE = 'po-context';\n\n/** Session selection and transition journal identities, not transport channels. */\nexport const PO_CONFIG_ENTRY_TYPE = 'po:config:v1';\nexport const PO_CONFIG_TRANSITION_ENTRY_TYPE = 'po:transition:v1';\n\n/** The tokenizer these counts came from, so a reader can judge them. */\nexport const CONTEXT_PROJECTION_ESTIMATOR = 'gpt-tokenizer';\n\nexport type ContextGroupKind = 'major' | 'minor' | 'domain' | 'shared' | 'core';\nexport type ContextItemSource = 'extension' | 'mcp' | 'plugin' | 'core';\n\n/** Everything the runtime could not attribute lands here rather than vanishing. */\nconst CORE_GROUP = 'core';\n/** Pi's own tools have no package; the runtime itself is the owner. */\nconst CORE_OWNER = 'pi';\nconst MCP_KEY_PREFIX = 'mcp:';\n\nconst KIND_ORDER: Record<ContextGroupKind, number> = { major: 0, minor: 1, domain: 2, shared: 3, core: 4 };\nconst SOURCE_ORDER: Record<ContextItemSource, number> = { extension: 0, mcp: 1, plugin: 2, core: 3 };\n\nexport interface ContextItemProjection {\n readonly name: string;\n readonly itemKind: 'tool' | 'skill';\n readonly source: ContextItemSource;\n /** The package, server, or plugin that registered it. */\n readonly owner: string;\n readonly tokens: number;\n readonly active: boolean;\n}\n\nexport interface ContextGroupProjection {\n readonly id: string;\n readonly label: string;\n readonly kind: ContextGroupKind;\n readonly items: readonly ContextItemProjection[];\n /** What the group costs now: active items only. */\n readonly tokens: number;\n /** What its gated items would add if switched on. */\n readonly inactiveTokens: number;\n}\n\n/**\n * What the assembled system prompt costs, and how complete the figure is.\n *\n * Kept out of `groups` on purpose. Every tool's prose and every skill's listing\n * already count inside the group subtotals, and those are slices of this same\n * text, so folding it into `totalTokens` would bill them twice.\n */\nexport interface ContextSystemPromptProjection {\n readonly tokens: number;\n readonly stage: ContextPromptStage;\n}\n\nexport interface ContextProjection {\n readonly version: typeof CONTEXT_PROJECTION_VERSION;\n readonly revision: number;\n /** Selection snapshot used to build this projection, replayable after reconnect. */\n readonly selection: {\n readonly majorMode: string;\n readonly domains: readonly string[];\n readonly profile?: string;\n /** Requested profile when none is active; null records an explicit None pick. Absent on legacy records. */\n readonly requestedProfile?: string | null;\n };\n readonly groups: readonly ContextGroupProjection[];\n readonly totalTokens: number;\n readonly inactiveTokens: number;\n readonly systemPrompt?: ContextSystemPromptProjection;\n readonly estimator: typeof CONTEXT_PROJECTION_ESTIMATOR;\n}\n\nexport interface ContextConditionalAttribution {\n readonly kind: 'minor' | 'domain' | 'shared';\n readonly mode: string;\n readonly label?: string;\n}\n\nexport interface ContextToolInventory extends ToolEntry {\n /** A contribution-level gate is more specific than its package's major-mode owner. */\n readonly contextAttribution?: ContextConditionalAttribution;\n}\n\nexport interface ContextToolSource extends Omit<ToolSource, 'tools'> {\n readonly tools: readonly ContextToolInventory[];\n}\n\n/** Skill fields needed to project cost and serve detail. Virtual resources may not have a filesystem path. */\nexport interface ContextSkillInventory {\n readonly name: string;\n readonly description: string;\n readonly group: 'extensions' | 'help' | 'plugins' | 'default';\n readonly owner: string;\n readonly modelInvocable: boolean;\n readonly promptTokens?: number;\n readonly filePath?: string;\n /** The skill's instructions, when the host holds them; read only when a reader opens the row. */\n readonly body?: string;\n readonly contextAttribution?: ContextConditionalAttribution;\n}\n\nexport interface ContextProjectionInput {\n readonly revision: number;\n readonly majorMode: string;\n readonly profile?: string;\n readonly requestedProfile?: string | null;\n /** Additional groups supplied by the composition owner, in display order. */\n readonly groups: readonly { readonly id: string; readonly label: string; readonly kind: ContextGroupKind }[];\n /** Domains the composition resolved under, whether or not they carried a plugin. */\n readonly domains: readonly string[];\n readonly sources: readonly ContextToolSource[];\n readonly skills: readonly ContextSkillInventory[];\n /**\n * Owner identifier to the mode that admitted it.\n *\n * Keys are package names for extensions and layer packages, and plugin or\n * server names for anything a domain carries. The caller owns this join\n * because only it knows how a given deployment resolved its plugins.\n */\n readonly attribution: Readonly<Record<string, PackageAttribution>>;\n readonly countTokens: CountTokens;\n /** Absent until the session has a prompt to report. */\n readonly systemPrompt?: ContextSystemPromptProjection;\n}\n\n/** Reads only replayable selection fields, accepting legacy projections without a request marker. */\nexport function readProjectedSelection(data: unknown): ContextProjection['selection'] | undefined {\n if (typeof data !== 'object' || data === null || Array.isArray(data) || !('selection' in data)) return undefined;\n const selection = data.selection;\n if (typeof selection !== 'object' || selection === null || Array.isArray(selection)) return undefined;\n if (!('majorMode' in selection) || typeof selection.majorMode !== 'string') return undefined;\n if (\n !('domains' in selection) ||\n !Array.isArray(selection.domains) ||\n !selection.domains.every((domain) => typeof domain === 'string')\n )\n return undefined;\n const profile = 'profile' in selection ? selection.profile : undefined;\n if (profile !== undefined && typeof profile !== 'string') return undefined;\n const requestedProfile = 'requestedProfile' in selection ? selection.requestedProfile : undefined;\n return {\n majorMode: selection.majorMode,\n domains: [...selection.domains],\n profile,\n ...(typeof requestedProfile === 'string' || requestedProfile === null ? { requestedProfile } : {}),\n };\n}\n\ninterface Bucket {\n kind: ContextGroupKind;\n label: string;\n items: ContextItemProjection[];\n}\n\nfunction skillSource(group: ContextSkillInventory['group']): ContextItemSource {\n if (group === 'plugins') return 'plugin';\n if (group === 'extensions') return 'extension';\n return 'core';\n}\n\nfunction byRow(left: ContextItemProjection, right: ContextItemProjection): number {\n return SOURCE_ORDER[left.source] - SOURCE_ORDER[right.source] || left.name.localeCompare(right.name);\n}\n\nexport function projectContext(input: ContextProjectionInput): ContextProjection {\n const buckets = new Map<string, Bucket>();\n\n // Declared before anything is placed, so a mode with nothing attributed to it\n // still appears. An empty group is a fact worth showing: it says the mode is\n // on and cheap, not that the runtime forgot about it.\n const declare = (id: string, label: string, kind: ContextGroupKind): void => {\n if (!buckets.has(id)) buckets.set(id, { kind, label, items: [] });\n };\n if (input.majorMode) declare(input.majorMode, input.majorMode, 'major');\n for (const group of input.groups) declare(group.id, group.label, group.kind);\n // A domain that contributed no plugin is still part of what the session is\n // running under, and a reader comparing the panel against the footer would\n // otherwise read its absence as the panel being out of date.\n for (const domain of input.domains) declare(domain, domain, 'domain');\n\n const place = (owner: string, item: ContextItemProjection, conditional?: ContextConditionalAttribution): void => {\n const attributed = conditional ?? input.attribution[owner];\n const id = attributed?.mode ?? CORE_GROUP;\n const kind = attributed?.kind ?? 'core';\n declare(id, attributed && 'label' in attributed ? (attributed.label ?? id) : id, kind);\n buckets.get(id)?.items.push(item);\n };\n\n for (const source of input.sources) {\n // An extension is owned by its package, an MCP tool by the server that\n // advertised it. `label` is display text like `scaffold · mcp`, so the\n // identifier comes from the key instead: an owner is joined on, not read.\n const owner =\n source.kind === 'extension'\n ? (source.packageName ?? source.key)\n : source.kind === 'mcp'\n ? source.key.slice(MCP_KEY_PREFIX.length)\n : CORE_OWNER;\n for (const tool of source.tools) {\n const cost = tokensForTool(tool, input.countTokens);\n const item: ContextItemProjection = {\n name: tool.name,\n itemKind: 'tool',\n source: source.kind === 'core' ? 'core' : source.kind,\n owner,\n tokens: cost.totalTokens,\n active: tool.active,\n };\n if (source.kind === 'core') {\n declare(CORE_GROUP, CORE_GROUP, 'core');\n buckets.get(CORE_GROUP)?.items.push(item);\n } else {\n place(owner, item, tool.contextAttribution);\n }\n }\n }\n\n for (const skill of input.skills) {\n place(\n skill.owner,\n {\n name: skill.name,\n itemKind: 'skill',\n source: skillSource(skill.group),\n owner: skill.owner,\n tokens: skill.promptTokens ?? 0,\n // A skill is always offered to the model; there is no inactive state for\n // it the way there is for a tool Pi has filtered out.\n active: true,\n },\n skill.contextAttribution,\n );\n }\n\n const groups: ContextGroupProjection[] = [...buckets.entries()]\n .map(([id, bucket]) => {\n const items = [...bucket.items].sort(byRow);\n // Only what is actually sent counts. Pi builds the prompt from its\n // active tools, so a gated one costs nothing until it is switched on,\n // and folding it into the total would overstate the bill by a third.\n return {\n id,\n label: bucket.label,\n kind: bucket.kind,\n items,\n tokens: items.reduce((total, item) => total + (item.active ? item.tokens : 0), 0),\n inactiveTokens: items.reduce((total, item) => total + (item.active ? 0 : item.tokens), 0),\n };\n })\n .sort((left, right) => KIND_ORDER[left.kind] - KIND_ORDER[right.kind] || left.label.localeCompare(right.label));\n\n return {\n version: CONTEXT_PROJECTION_VERSION,\n revision: input.revision,\n selection: {\n majorMode: input.majorMode,\n domains: [...input.domains],\n ...(input.profile === undefined ? {} : { profile: input.profile }),\n ...(input.requestedProfile === undefined ? {} : { requestedProfile: input.requestedProfile }),\n },\n groups,\n totalTokens: groups.reduce((total, group) => total + group.tokens, 0),\n inactiveTokens: groups.reduce((total, group) => total + group.inactiveTokens, 0),\n ...(input.systemPrompt === undefined ? {} : { systemPrompt: input.systemPrompt }),\n estimator: CONTEXT_PROJECTION_ESTIMATOR,\n };\n}\n"],"mappings":"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"}
@@ -75,6 +75,8 @@ export interface HeadlessSessionHostOptions {
75
75
  environment: Readonly<Record<string, string | undefined>>;
76
76
  selection: PoHeadlessSelection;
77
77
  selectionOverrides?: readonly ('majorMode' | 'domains' | 'profile')[];
78
+ /** Profile intent admitted by the launcher, retained when the requested profile is unavailable. */
79
+ requestedProfile?: string | null;
78
80
  /** The only tools the agent may see or call, whatever the selection and packages contribute. */
79
81
  allowedTools?: readonly string[];
80
82
  inheritedSelection?: () => Partial<PoHeadlessSelection> | Promise<Partial<PoHeadlessSelection>>;
@@ -1 +1 @@
1
- {"version":3,"file":"headlessSessionHost.d.mts","names":[],"sources":["../src/systems/main/types/headlessSessionHost.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;YAwBY,iBAAiB,SAC3B,KACE;;YAYQ,4BAA4B,KACtC;;iBAKe;;WAEN;WACA,cAAc;;WAEd;aAAyC;aAAuB;;;EAEzE,aAAa,QAAQ,OAAO;IAAU,OAAO,MAAM;IAAM,gBAAgB;;;WAEhE,gBAAgB,OAAO,8BAA8B;;iBAE/C;EACf;;EAEA,UAAU;EACV;EACA;;EAEA;EACA;;EAEA;EACA,iBAAiB;EACjB,oBAAoB;EACpB;;EAEA;;EAEA;EACA;EACA;;EAEA;;WAES,kBAAkB;EAC3B;EACA,aAAa,SAAS;EACtB,WAAW;EACX;;EAEA;EACA,2BAA2B,QAAQ,uBAAuB,QAAQ,QAAQ;EAC1E,qBAAqB;;EAErB,+BAA+B;;EAE/B,mCAAmC;;EAEnC,sBAAsB;EACtB,mBAAmB;EACnB,0BACE,YAAY,gBAAgB,mCAC5B,WAAW;EAEb,iBAAiB,SAAS,SAAS,WAAW,wBAAwB;EACtE,YAAY;;EAEZ;EACA,YAAY,KAAK,kCAAkC,QAAQ,KAAK;;EAEhE;;EAEA,qBAAqB,SAAS,+BAA+B,QAAQ;;iBAKtD;WACN,SAAS;;;;;WAKT,eAAe;EACxB,uBAAuB,QAAQ;;EAE/B,WAAW,qBAAqB;;WAEvB,iBAAiB;WACjB,MAAM;WACN,aAAa;WACb,YAAY;;WAEZ,kBAAkB,MAAM;;WAExB,mBAAmB,yCAAyC;;EAErE,gBAAgB,qBAAqB,uBAAuB,uCAAuC;;EAEnG,kBAAkB,eAAe,0BAA0B;WAClD;;EAET,aAAa,gBAAgB;EAC7B,oBAAoB,WAAW,OAAO;;EAEtC,aAAa,WAAW,WAAW;;EAEnC,kBAAkB,WAAW;EAC7B,qBAAqB,OAAO;;EAE5B,mBAAmB;EACnB,QAAQ,UAAU,8BAA8B"}
1
+ {"version":3,"file":"headlessSessionHost.d.mts","names":[],"sources":["../src/systems/main/types/headlessSessionHost.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;YAwBY,iBAAiB,SAC3B,KACE;;YAYQ,4BAA4B,KACtC;;iBAKe;;WAEN;WACA,cAAc;;WAEd;aAAyC;aAAuB;;;EAEzE,aAAa,QAAQ,OAAO;IAAU,OAAO,MAAM;IAAM,gBAAgB;;;WAEhE,gBAAgB,OAAO,8BAA8B;;iBAE/C;EACf;;EAEA,UAAU;EACV;EACA;;EAEA;EACA;;EAEA;EACA,iBAAiB;EACjB,oBAAoB;EACpB;;EAEA;;EAEA;EACA;EACA;;EAEA;;WAES,kBAAkB;EAC3B;EACA,aAAa,SAAS;EACtB,WAAW;EACX;;EAEA;;EAEA;EACA,2BAA2B,QAAQ,uBAAuB,QAAQ,QAAQ;EAC1E,qBAAqB;;EAErB,+BAA+B;;EAE/B,mCAAmC;;EAEnC,sBAAsB;EACtB,mBAAmB;EACnB,0BACE,YAAY,gBAAgB,mCAC5B,WAAW;EAEb,iBAAiB,SAAS,SAAS,WAAW,wBAAwB;EACtE,YAAY;;EAEZ;EACA,YAAY,KAAK,kCAAkC,QAAQ,KAAK;;EAEhE;;EAEA,qBAAqB,SAAS,+BAA+B,QAAQ;;iBAKtD;WACN,SAAS;;;;;WAKT,eAAe;EACxB,uBAAuB,QAAQ;;EAE/B,WAAW,qBAAqB;;WAEvB,iBAAiB;WACjB,MAAM;WACN,aAAa;WACb,YAAY;;WAEZ,kBAAkB,MAAM;;WAExB,mBAAmB,yCAAyC;;EAErE,gBAAgB,qBAAqB,uBAAuB,uCAAuC;;EAEnG,kBAAkB,eAAe,0BAA0B;WAClD;;EAET,aAAa,gBAAgB;EAC7B,oBAAoB,WAAW,OAAO;;EAEtC,aAAa,WAAW,WAAW;;EAEnC,kBAAkB,WAAW;EAC7B,qBAAqB,OAAO;;EAE5B,mBAAmB;EACnB,QAAQ,UAAU,8BAA8B"}
package/dist/history.cjs CHANGED
@@ -19,6 +19,7 @@ exports.identifyWorkspace = require_sync$1.identifyWorkspace;
19
19
  exports.listSavedSessionRecords = require_index.listSavedSessionRecords;
20
20
  exports.listSavedSessions = require_index.listSavedSessions;
21
21
  exports.localWorkspaceRecords = require_workspaceRecords.localWorkspaceRecords;
22
+ exports.readSavedSelection = require_index.readSavedSelection;
22
23
  exports.readSqliteTranscript = require_index$1.readSqliteTranscript;
23
24
  exports.readWorkspaceMarker = require_sync$1.readWorkspaceMarker;
24
25
  exports.validateDurableSessionFile = require_sqliteSessionStorage.validateDurableSessionFile;
@@ -1,6 +1,6 @@
1
1
  import { HistoryOwnership, HistoryOwnershipLease } from "./src/services/historyOwnership/type.mjs";
2
2
  import { HistoryOwnershipOptions, createHistoryOwnership, createV4HistoryOwnership, historyOwnershipLockPath } from "./historyOwnership.mjs";
3
- import { RemovedSession, SavedSessionExecution, SavedSessionRecord, listSavedSessionRecords, listSavedSessions } from "./src/services/sqliteSessionHistory/index.mjs";
3
+ import { RemovedSession, SavedSessionExecution, SavedSessionRecord, listSavedSessionRecords, listSavedSessions, readSavedSelection } from "./src/services/sqliteSessionHistory/index.mjs";
4
4
  import { SqliteTranscriptOwnership, readSqliteTranscript } from "./src/services/sqliteTranscriptReader/index.mjs";
5
5
  import { validateDurableSessionFile } from "./sqliteSessionStorage.mjs";
6
6
  import { OpenSessionRecord, OpenSessionRegistry, OpenSessionRegistryOptions } from "./src/services/openSessionRegistry/openSessionRecords.mjs";
@@ -10,4 +10,4 @@ import { WorkspaceIdentity, WorkspaceRecord, WorkspaceRegistry, WorkspaceRegistr
10
10
  import { createWorkspaceRegistry, identifyWorkspace, readWorkspaceMarker, writeWorkspaceMarker } from "./src/services/workspaceRegistry/sync.mjs";
11
11
  import "./src/services/workspaceRegistry/index.mjs";
12
12
  import { createRequestReceipts } from "./src/services/durableRequestReceipts/index.mjs";
13
- export { type HistoryOwnership, type HistoryOwnershipLease, type HistoryOwnershipOptions, type OpenSessionRecord, type OpenSessionRegistry, type OpenSessionRegistryOptions, type RemovedSession, type SavedSessionExecution, type SavedSessionRecord, type SqliteTranscriptOwnership, type WorkspaceIdentity, type WorkspaceRecord, type WorkspaceRegistry, type WorkspaceRegistryOptions, createHistoryOwnership, createOpenSessionRegistry, createRequestReceipts, createV4HistoryOwnership, createWorkspaceRegistry, historyOwnershipLockPath, identifyWorkspace, listSavedSessionRecords, listSavedSessions, localWorkspaceRecords, readSqliteTranscript, readWorkspaceMarker, validateDurableSessionFile, writeWorkspaceMarker };
13
+ export { type HistoryOwnership, type HistoryOwnershipLease, type HistoryOwnershipOptions, type OpenSessionRecord, type OpenSessionRegistry, type OpenSessionRegistryOptions, type RemovedSession, type SavedSessionExecution, type SavedSessionRecord, type SqliteTranscriptOwnership, type WorkspaceIdentity, type WorkspaceRecord, type WorkspaceRegistry, type WorkspaceRegistryOptions, createHistoryOwnership, createOpenSessionRegistry, createRequestReceipts, createV4HistoryOwnership, createWorkspaceRegistry, historyOwnershipLockPath, identifyWorkspace, listSavedSessionRecords, listSavedSessions, localWorkspaceRecords, readSavedSelection, readSqliteTranscript, readWorkspaceMarker, validateDurableSessionFile, writeWorkspaceMarker };
package/dist/history.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createHistoryOwnership, createV4HistoryOwnership, historyOwnershipLockPath } from "./historyOwnership.mjs";
2
2
  import { validateDurableSessionFile } from "./sqliteSessionStorage.mjs";
3
- import { listSavedSessionRecords, listSavedSessions } from "./src/services/sqliteSessionHistory/index.mjs";
3
+ import { listSavedSessionRecords, listSavedSessions, readSavedSelection } from "./src/services/sqliteSessionHistory/index.mjs";
4
4
  import { readSqliteTranscript } from "./src/services/sqliteTranscriptReader/index.mjs";
5
5
  import { createOpenSessionRegistry } from "./src/services/openSessionRegistry/sync.mjs";
6
6
  import "./src/services/openSessionRegistry/index.mjs";
@@ -8,4 +8,4 @@ import { localWorkspaceRecords } from "./src/services/workspaceRegistry/workspac
8
8
  import { createWorkspaceRegistry, identifyWorkspace, readWorkspaceMarker, writeWorkspaceMarker } from "./src/services/workspaceRegistry/sync.mjs";
9
9
  import "./src/services/workspaceRegistry/index.mjs";
10
10
  import { createRequestReceipts } from "./src/services/durableRequestReceipts/index.mjs";
11
- export { createHistoryOwnership, createOpenSessionRegistry, createRequestReceipts, createV4HistoryOwnership, createWorkspaceRegistry, historyOwnershipLockPath, identifyWorkspace, listSavedSessionRecords, listSavedSessions, localWorkspaceRecords, readSqliteTranscript, readWorkspaceMarker, validateDurableSessionFile, writeWorkspaceMarker };
11
+ export { createHistoryOwnership, createOpenSessionRegistry, createRequestReceipts, createV4HistoryOwnership, createWorkspaceRegistry, historyOwnershipLockPath, identifyWorkspace, listSavedSessionRecords, listSavedSessions, localWorkspaceRecords, readSavedSelection, readSqliteTranscript, readWorkspaceMarker, validateDurableSessionFile, writeWorkspaceMarker };
@@ -80,20 +80,21 @@ function createProviderAuthOwner(options) {
80
80
  pending = pending.then(recompute, recompute);
81
81
  return pending;
82
82
  };
83
- const models = (0, _earendil_works_pi_ai.createModels)({
84
- credentials: {
85
- read: (providerId, operation) => options.credentials.read(providerId, operation),
86
- list: (operation) => options.credentials.list(operation),
87
- modify: async (providerId, fn, operation) => {
88
- const result = await options.credentials.modify(providerId, fn, operation);
89
- changed().catch(report);
90
- return result;
91
- },
92
- delete: async (providerId, operation) => {
93
- await options.credentials.delete(providerId, operation);
94
- changed().catch(report);
95
- }
83
+ const credentials = {
84
+ read: (providerId, operation) => options.credentials.read(providerId, operation),
85
+ list: (operation) => options.credentials.list(operation),
86
+ modify: async (providerId, fn, operation) => {
87
+ const result = await options.credentials.modify(providerId, fn, operation);
88
+ changed().catch(report);
89
+ return result;
96
90
  },
91
+ delete: async (providerId, operation) => {
92
+ await options.credentials.delete(providerId, operation);
93
+ changed().catch(report);
94
+ }
95
+ };
96
+ const models = (0, _earendil_works_pi_ai.createModels)({
97
+ credentials,
97
98
  modelsStore: options.modelsStore,
98
99
  authContext: NO_AMBIENT_AUTH
99
100
  });
@@ -110,6 +111,7 @@ function createProviderAuthOwner(options) {
110
111
  let current;
111
112
  async function recompute() {
112
113
  const providers = models.getProviders();
114
+ const stored = new Map((await credentials.list()).map(({ providerId, type }) => [providerId, type]));
113
115
  const checks = await Promise.all(providers.map(async (provider) => ({
114
116
  provider,
115
117
  check: await models.checkAuth(provider.id).catch(() => void 0)
@@ -124,7 +126,7 @@ function createProviderAuthOwner(options) {
124
126
  providers: checks.flatMap(({ provider, check }) => check === void 0 ? [] : [{
125
127
  id: provider.id,
126
128
  type: check.type,
127
- ...check.source === void 0 ? {} : { source: check.source },
129
+ source: stored.get(provider.id) === check.type ? "stored" : check.source,
128
130
  subscription: check.type === "oauth" && provider.auth.oauth?.isSubscription === true
129
131
  }]),
130
132
  available: (await models.getAllAvailable()).map((model) => ({
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["openAICompletionsApi","openAIResponsesApi","anthropicMessagesApi","createProvider","envApiKeyAuth","createModels","builtinProviders","getModelType","ModelsError"],"sources":["../../../../src/services/edgeModelRuntime/index.ts"],"sourcesContent":["import {\n createModels,\n createProvider,\n envApiKeyAuth,\n getModelType,\n ModelsError,\n type AnyModel,\n type AuthContext,\n type AuthInteraction,\n type CredentialStore,\n type ModelsStore,\n type ModelsStoreEntry,\n type Provider,\n} from '@earendil-works/pi-ai';\nimport { anthropicMessagesApi } from '@earendil-works/pi-ai/api/anthropic-messages.lazy';\nimport { openAICompletionsApi } from '@earendil-works/pi-ai/api/openai-completions.lazy';\nimport { openAIResponsesApi } from '@earendil-works/pi-ai/api/openai-responses.lazy';\nimport { builtinProviders } from '@earendil-works/pi-ai/providers/all';\n\nimport type { PoCustomProviderDefinition, PoProviderAuth, PoProviderAuthSnapshot } from '../../schemas/platform';\nimport type { PoModelRuntime } from '../../systems/main/types/headlessSessionHost';\nimport type { AuthRuntime } from '../../types/server/auth';\nimport type { OwnerBackedModelRuntimeOptions, ProviderAuthOwner, ProviderAuthOwnerOptions } from './type';\n\nexport type { OwnerBackedModelRuntimeOptions, ProviderAuthOwner, ProviderAuthOwnerOptions } from './type';\n\nconst MANAGED_BY_ADMINS = 'Org admins manage providers in Settings → Providers.';\n\n/** No env vars or ambient files: on a shared host they belong to the host, not to the deployment target. */\nconst NO_AMBIENT_AUTH: AuthContext = { env: async () => undefined, fileExists: async () => false };\n\nconst CUSTOM_API = {\n 'openai-completions': openAICompletionsApi,\n 'openai-responses': openAIResponsesApi,\n 'anthropic-messages': anthropicMessagesApi,\n} as const;\n\n/** A hand-added endpoint as a provider, with the defaults Pi gives a models.json provider. */\nexport function customProvider(definition: PoCustomProviderDefinition): Provider {\n return createProvider({\n id: definition.id,\n name: definition.name,\n baseUrl: definition.baseUrl,\n auth: { apiKey: envApiKeyAuth(`${definition.name} API key`, []) },\n models: definition.models.map((model) => ({\n id: model.id,\n name: model.name ?? model.id,\n api: definition.api,\n provider: definition.id,\n baseUrl: definition.baseUrl,\n reasoning: model.reasoning ?? false,\n input: [...(model.input ?? ['text'])],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: model.contextWindow ?? 128000,\n maxTokens: model.maxTokens ?? 16384,\n })),\n api: CUSTOM_API[definition.api](),\n });\n}\n\n/** A URL without userinfo, query or fragment; unchanged when it carries none, so path joins keep working. */\nfunction publicUrl(value: string): string {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return '';\n }\n if (url.username === '' && url.password === '' && url.search === '' && url.hash === '') return value;\n url.username = '';\n url.password = '';\n url.search = '';\n url.hash = '';\n return url.href;\n}\n\n/** A dynamic catalog fit to leave the owner: no headers, no URL secrets, data only. */\nexport function sanitizeModelsEntry(entry: ModelsStoreEntry): ModelsStoreEntry {\n return JSON.parse(\n JSON.stringify({\n ...entry,\n models: entry.models.map(({ headers: _headers, ...model }) => ({ ...model, baseUrl: publicUrl(model.baseUrl) })),\n }),\n ) as ModelsStoreEntry;\n}\n\nconst availableKey = (model: Pick<AnyModel, 'provider' | 'id'> & { type?: string }): string =>\n `${model.type ?? 'chat'}\\0${model.provider}\\0${model.id}`;\n\n/**\n * The owner side: a real pi-ai `Models` over the deployment target's credential store, so login, OAuth\n * refresh and request auth behave exactly as on a local machine. Every change that sessions can see\n * produces a new snapshot version.\n */\nexport function createProviderAuthOwner(options: ProviderAuthOwnerOptions): ProviderAuthOwner {\n // A failed recompute stays in `pending`, so settled() rethrows it to the next caller; the next change retries.\n const report = options.onError ?? ((): void => undefined);\n let pending: Promise<void> = Promise.resolve();\n const changed = (): Promise<void> => {\n pending = pending.then(recompute, recompute);\n return pending;\n };\n const credentials: CredentialStore = {\n read: (providerId, operation) => options.credentials.read(providerId, operation),\n list: (operation) => options.credentials.list(operation),\n modify: async (providerId, fn, operation) => {\n const result = await options.credentials.modify(providerId, fn, operation);\n void changed().catch(report);\n return result;\n },\n delete: async (providerId, operation) => {\n await options.credentials.delete(providerId, operation);\n void changed().catch(report);\n },\n };\n const models = createModels({ credentials, modelsStore: options.modelsStore, authContext: NO_AMBIENT_AUTH });\n for (const provider of (options.providers ?? builtinProviders)()) models.setProvider(provider);\n\n let customProviders: readonly PoCustomProviderDefinition[] = [];\n const applyCustomProviders = (definitions: readonly PoCustomProviderDefinition[]): void => {\n for (const previous of customProviders)\n if (!definitions.some((definition) => definition.id === previous.id)) models.deleteProvider(previous.id);\n for (const definition of definitions) models.setProvider(customProvider(definition));\n customProviders = definitions;\n };\n applyCustomProviders(options.customProviders ?? []);\n\n // Starts from the clock, so a restarted owner never repeats a version a session still holds.\n let version = Date.now();\n let lastBody = '';\n let current: PoProviderAuthSnapshot | undefined;\n async function recompute(): Promise<void> {\n const providers = models.getProviders();\n const checks = await Promise.all(\n providers.map(async (provider) => ({\n provider,\n check: await models.checkAuth(provider.id).catch(() => undefined),\n })),\n );\n const catalogs: Record<string, ModelsStoreEntry> = {};\n for (const provider of providers) {\n if (provider.refreshModels === undefined) continue;\n const entry = await options.modelsStore.read(provider.id);\n if (entry !== undefined) catalogs[provider.id] = sanitizeModelsEntry(entry);\n }\n const body = {\n providers: checks.flatMap(({ provider, check }) =>\n check === undefined\n ? []\n : [\n {\n id: provider.id,\n type: check.type,\n ...(check.source === undefined ? {} : { source: check.source }),\n subscription: check.type === 'oauth' && provider.auth.oauth?.isSubscription === true,\n },\n ],\n ),\n available: (await models.getAllAvailable()).map((model) => ({\n provider: model.provider,\n id: model.id,\n type: getModelType(model),\n })),\n models: catalogs,\n customProviders,\n };\n const serialized = JSON.stringify(body);\n if (serialized === lastBody && current !== undefined) return;\n lastBody = serialized;\n version += 1;\n current = { version, ...(JSON.parse(serialized) as Omit<PoProviderAuthSnapshot, 'version'>) };\n }\n const settled = async (): Promise<PoProviderAuthSnapshot> => {\n await pending;\n if (current === undefined) await changed();\n return current as PoProviderAuthSnapshot;\n };\n void changed().catch(report);\n\n const statusOf = (providerId: string) => current?.providers.find((entry) => entry.id === providerId);\n const refresh: PoProviderAuth['refresh'] = async (refreshOptions) => {\n const result = await models.refresh({ allowNetwork: refreshOptions?.allowNetwork ?? true });\n await changed();\n const snapshot = await settled();\n return {\n version: snapshot.version,\n errors: Object.fromEntries([...result.errors].map(([providerId, error]) => [providerId, error.message])),\n };\n };\n\n const service: PoProviderAuth = {\n abiVersion: 1,\n resolveAuth: async (providerId) => {\n const auth = await models.getAuth(providerId);\n return { version: (await settled()).version, ...(auth === undefined ? {} : { auth }) };\n },\n snapshot: async (sinceVersion) => {\n const snapshot = await settled();\n return sinceVersion === snapshot.version ? { version: snapshot.version } : snapshot;\n },\n refresh,\n };\n\n const authRuntime: AuthRuntime = {\n getProviders: () => models.getProviders(),\n getProviderAuthStatus: (providerId) => {\n const status = statusOf(providerId);\n return status === undefined ? { configured: false } : { configured: true, source: status.source };\n },\n getAvailableSnapshot: () => (current?.available ?? []).filter((model) => model.type === 'chat'),\n hasConfiguredAuth: (providerId) => statusOf(providerId) !== undefined,\n isUsingOAuth: (providerId) => statusOf(providerId)?.type === 'oauth',\n refresh: (refreshOptions) => refresh(refreshOptions),\n login: async (providerId, type, interaction) => {\n await models.login(providerId, type, interaction as AuthInteraction);\n await settled();\n },\n logout: async (providerId, logoutOptions) => {\n await models.logout(providerId, logoutOptions);\n await settled();\n },\n };\n\n return {\n service,\n authRuntime,\n setCustomProviders: async (definitions) => {\n applyCustomProviders(definitions);\n await changed();\n },\n };\n}\n\n/** Session credentials stay empty: every request's auth comes from the owner and is never kept. */\nconst NO_SESSION_CREDENTIALS: CredentialStore = {\n read: async () => undefined,\n list: async () => [],\n modify: async () => {\n throw new ModelsError('auth', MANAGED_BY_ADMINS);\n },\n delete: async () => {\n throw new ModelsError('auth', MANAGED_BY_ADMINS);\n },\n};\n\n/**\n * The session side: pi-ai `Models` with the same providers as the owner, whose auth asks the owner and\n * whose status and availability come from the owner's snapshot. Requests, streaming and header merges\n * run locally, unchanged; the session never holds a credential.\n */\nexport async function createOwnerBackedModelRuntime(options: OwnerBackedModelRuntimeOptions): Promise<PoModelRuntime> {\n const { owner } = options;\n let snapshot: PoProviderAuthSnapshot | undefined;\n let availableKeys = new Set<string>();\n const statusOf = (providerId: string) => snapshot?.providers.find((entry) => entry.id === providerId);\n const isAvailable = (model: AnyModel): boolean =>\n availableKeys.has(availableKey({ provider: model.provider, id: model.id, type: getModelType(model) }));\n\n const modelsStore: ModelsStore = {\n read: async (providerId) => snapshot?.models[providerId],\n // The owner persists catalogs; a session only restores the snapshot's copy.\n write: async () => undefined,\n delete: async () => undefined,\n };\n const models = createModels({\n credentials: NO_SESSION_CREDENTIALS,\n modelsStore,\n authContext: NO_AMBIENT_AUTH,\n });\n\n // A failed sync keeps the last snapshot; the next version the owner reports retries it.\n const report = options.onError ?? ((): void => undefined);\n let syncing: Promise<void> = Promise.resolve();\n const pull = async (): Promise<void> => {\n const next = await owner.snapshot(snapshot?.version);\n if (!('providers' in next)) return;\n const previousCustom = snapshot?.customProviders ?? [];\n snapshot = next;\n availableKeys = new Set(next.available.map(availableKey));\n for (const previous of previousCustom)\n if (!next.customProviders.some((definition) => definition.id === previous.id)) models.deleteProvider(previous.id);\n for (const definition of next.customProviders) models.setProvider(ownerBacked(customProvider(definition)));\n await models.refresh({ allowNetwork: false });\n };\n const sync = (): Promise<void> => {\n syncing = syncing.then(pull, pull);\n return syncing;\n };\n const observe = (version: number): void => {\n if (version !== snapshot?.version) void sync().catch(report);\n };\n\n const ownerBacked = (provider: Provider): Provider => {\n const apiKey = provider.auth.apiKey;\n return {\n ...provider,\n auth: {\n ...provider.auth,\n apiKey: {\n name: apiKey?.name ?? provider.name,\n check: async () => {\n const status = statusOf(provider.id);\n return status === undefined\n ? undefined\n : { type: status.type, ...(status.source === undefined ? {} : { source: status.source }) };\n },\n resolve: async ({ credential, signal }) => {\n // A key the caller passed for this request resolves locally, as it would on a machine.\n if (credential?.key !== undefined && apiKey !== undefined)\n return apiKey.resolve({ ctx: NO_AMBIENT_AUTH, credential, signal });\n const result = await owner.resolveAuth(provider.id);\n observe(result.version);\n return result.auth;\n },\n },\n },\n filterModels: (list) => list.filter(isAvailable),\n filterAllModels: (list) => list.filter(isAvailable),\n } as Provider;\n };\n for (const provider of (options.providers ?? builtinProviders)()) models.setProvider(ownerBacked(provider));\n await sync();\n\n const managedByAdmins = async (): Promise<never> => {\n throw new ModelsError('auth', MANAGED_BY_ADMINS);\n };\n const refused = (): never => {\n throw new ModelsError('provider', MANAGED_BY_ADMINS);\n };\n return {\n getProviders: models.getProviders.bind(models),\n getProvider: models.getProvider.bind(models),\n getModels: models.getModels.bind(models),\n getModel: models.getModel.bind(models),\n getModelsOfType: models.getModelsOfType.bind(models),\n getModelOfType: models.getModelOfType.bind(models),\n getAllModels: models.getAllModels.bind(models),\n checkAuth: models.checkAuth.bind(models),\n getAvailable: models.getAvailable.bind(models),\n getAvailableOfType: models.getAvailableOfType.bind(models),\n getAllAvailable: models.getAllAvailable.bind(models),\n getAuth: models.getAuth.bind(models),\n stream: models.stream.bind(models),\n complete: models.complete.bind(models),\n streamSimple: models.streamSimple.bind(models),\n completeSimple: models.completeSimple.bind(models),\n streamDeferred: models.streamDeferred.bind(models),\n fetchDeferred: models.fetchDeferred.bind(models),\n cancelDeferred: models.cancelDeferred.bind(models),\n generateImages: models.generateImages.bind(models),\n classify: models.classify.bind(models),\n refresh: async (refreshOptions?: { allowNetwork?: boolean }) => {\n const result = await owner.refresh({ allowNetwork: refreshOptions?.allowNetwork ?? true });\n if (result.version !== snapshot?.version) await sync();\n return {\n aborted: false,\n errors: new Map(\n Object.entries(result.errors).map(([providerId, message]) => [\n providerId,\n new ModelsError('model_source', message),\n ]),\n ),\n };\n },\n getAvailableSnapshot: () => models.getModels().filter(isAvailable),\n getError: () => undefined,\n hasConfiguredAuth: (providerId: string) => statusOf(providerId) !== undefined,\n isUsingOAuth: (providerId: string) => statusOf(providerId)?.type === 'oauth',\n isUsingSubscription: (providerId: string) => statusOf(providerId)?.subscription === true,\n getProviderAuthStatus: (providerId: string) => {\n const status = statusOf(providerId);\n return status === undefined ? { configured: false } : { configured: true, source: status.source };\n },\n login: managedByAdmins,\n logout: managedByAdmins,\n setRuntimeApiKey: managedByAdmins,\n removeRuntimeApiKey: managedByAdmins,\n listCredentials: async () => [],\n getRegisteredNativeProvider: () => undefined,\n getRegisteredProviderConfig: () => undefined,\n getRegisteredProviderIds: () => [],\n getCompatibilityRequestConfig: refused,\n registerProvider: refused,\n registerNativeProvider: refused,\n unregisterProvider: refused,\n registerVirtualModel: refused,\n unregisterVirtualModel: refused,\n } as unknown as PoModelRuntime;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["openAICompletionsApi","openAIResponsesApi","anthropicMessagesApi","createProvider","envApiKeyAuth","createModels","builtinProviders","getModelType","ModelsError"],"sources":["../../../../src/services/edgeModelRuntime/index.ts"],"sourcesContent":["import {\n createModels,\n createProvider,\n envApiKeyAuth,\n getModelType,\n ModelsError,\n type AnyModel,\n type AuthContext,\n type AuthInteraction,\n type CredentialStore,\n type ModelsStore,\n type ModelsStoreEntry,\n type Provider,\n} from '@earendil-works/pi-ai';\nimport { anthropicMessagesApi } from '@earendil-works/pi-ai/api/anthropic-messages.lazy';\nimport { openAICompletionsApi } from '@earendil-works/pi-ai/api/openai-completions.lazy';\nimport { openAIResponsesApi } from '@earendil-works/pi-ai/api/openai-responses.lazy';\nimport { builtinProviders } from '@earendil-works/pi-ai/providers/all';\n\nimport type { PoCustomProviderDefinition, PoProviderAuth, PoProviderAuthSnapshot } from '../../schemas/platform';\nimport type { PoModelRuntime } from '../../systems/main/types/headlessSessionHost';\nimport type { AuthRuntime } from '../../types/server/auth';\nimport type { OwnerBackedModelRuntimeOptions, ProviderAuthOwner, ProviderAuthOwnerOptions } from './type';\n\nexport type { OwnerBackedModelRuntimeOptions, ProviderAuthOwner, ProviderAuthOwnerOptions } from './type';\n\nconst MANAGED_BY_ADMINS = 'Org admins manage providers in Settings → Providers.';\n\n/** No env vars or ambient files: on a shared host they belong to the host, not to the deployment target. */\nconst NO_AMBIENT_AUTH: AuthContext = { env: async () => undefined, fileExists: async () => false };\n\nconst CUSTOM_API = {\n 'openai-completions': openAICompletionsApi,\n 'openai-responses': openAIResponsesApi,\n 'anthropic-messages': anthropicMessagesApi,\n} as const;\n\n/** A hand-added endpoint as a provider, with the defaults Pi gives a models.json provider. */\nexport function customProvider(definition: PoCustomProviderDefinition): Provider {\n return createProvider({\n id: definition.id,\n name: definition.name,\n baseUrl: definition.baseUrl,\n auth: { apiKey: envApiKeyAuth(`${definition.name} API key`, []) },\n models: definition.models.map((model) => ({\n id: model.id,\n name: model.name ?? model.id,\n api: definition.api,\n provider: definition.id,\n baseUrl: definition.baseUrl,\n reasoning: model.reasoning ?? false,\n input: [...(model.input ?? ['text'])],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: model.contextWindow ?? 128000,\n maxTokens: model.maxTokens ?? 16384,\n })),\n api: CUSTOM_API[definition.api](),\n });\n}\n\n/** A URL without userinfo, query or fragment; unchanged when it carries none, so path joins keep working. */\nfunction publicUrl(value: string): string {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return '';\n }\n if (url.username === '' && url.password === '' && url.search === '' && url.hash === '') return value;\n url.username = '';\n url.password = '';\n url.search = '';\n url.hash = '';\n return url.href;\n}\n\n/** A dynamic catalog fit to leave the owner: no headers, no URL secrets, data only. */\nexport function sanitizeModelsEntry(entry: ModelsStoreEntry): ModelsStoreEntry {\n return JSON.parse(\n JSON.stringify({\n ...entry,\n models: entry.models.map(({ headers: _headers, ...model }) => ({ ...model, baseUrl: publicUrl(model.baseUrl) })),\n }),\n ) as ModelsStoreEntry;\n}\n\nconst availableKey = (model: Pick<AnyModel, 'provider' | 'id'> & { type?: string }): string =>\n `${model.type ?? 'chat'}\\0${model.provider}\\0${model.id}`;\n\n/**\n * The owner side: a real pi-ai `Models` over the deployment target's credential store, so login, OAuth\n * refresh and request auth behave exactly as on a local machine. Every change that sessions can see\n * produces a new snapshot version.\n */\nexport function createProviderAuthOwner(options: ProviderAuthOwnerOptions): ProviderAuthOwner {\n // A failed recompute stays in `pending`, so settled() rethrows it to the next caller; the next change retries.\n const report = options.onError ?? ((): void => undefined);\n let pending: Promise<void> = Promise.resolve();\n const changed = (): Promise<void> => {\n pending = pending.then(recompute, recompute);\n return pending;\n };\n const credentials: CredentialStore = {\n read: (providerId, operation) => options.credentials.read(providerId, operation),\n list: (operation) => options.credentials.list(operation),\n modify: async (providerId, fn, operation) => {\n const result = await options.credentials.modify(providerId, fn, operation);\n void changed().catch(report);\n return result;\n },\n delete: async (providerId, operation) => {\n await options.credentials.delete(providerId, operation);\n void changed().catch(report);\n },\n };\n const models = createModels({ credentials, modelsStore: options.modelsStore, authContext: NO_AMBIENT_AUTH });\n for (const provider of (options.providers ?? builtinProviders)()) models.setProvider(provider);\n\n let customProviders: readonly PoCustomProviderDefinition[] = [];\n const applyCustomProviders = (definitions: readonly PoCustomProviderDefinition[]): void => {\n for (const previous of customProviders)\n if (!definitions.some((definition) => definition.id === previous.id)) models.deleteProvider(previous.id);\n for (const definition of definitions) models.setProvider(customProvider(definition));\n customProviders = definitions;\n };\n applyCustomProviders(options.customProviders ?? []);\n\n // Starts from the clock, so a restarted owner never repeats a version a session still holds.\n let version = Date.now();\n let lastBody = '';\n let current: PoProviderAuthSnapshot | undefined;\n async function recompute(): Promise<void> {\n const providers = models.getProviders();\n const stored = new Map((await credentials.list()).map(({ providerId, type }) => [providerId, type]));\n const checks = await Promise.all(\n providers.map(async (provider) => ({\n provider,\n check: await models.checkAuth(provider.id).catch(() => undefined),\n })),\n );\n const catalogs: Record<string, ModelsStoreEntry> = {};\n for (const provider of providers) {\n if (provider.refreshModels === undefined) continue;\n const entry = await options.modelsStore.read(provider.id);\n if (entry !== undefined) catalogs[provider.id] = sanitizeModelsEntry(entry);\n }\n const body = {\n providers: checks.flatMap(({ provider, check }) =>\n check === undefined\n ? []\n : [\n {\n id: provider.id,\n type: check.type,\n source: stored.get(provider.id) === check.type ? 'stored' : check.source,\n subscription: check.type === 'oauth' && provider.auth.oauth?.isSubscription === true,\n },\n ],\n ),\n available: (await models.getAllAvailable()).map((model) => ({\n provider: model.provider,\n id: model.id,\n type: getModelType(model),\n })),\n models: catalogs,\n customProviders,\n };\n const serialized = JSON.stringify(body);\n if (serialized === lastBody && current !== undefined) return;\n lastBody = serialized;\n version += 1;\n current = { version, ...(JSON.parse(serialized) as Omit<PoProviderAuthSnapshot, 'version'>) };\n }\n const settled = async (): Promise<PoProviderAuthSnapshot> => {\n await pending;\n if (current === undefined) await changed();\n return current as PoProviderAuthSnapshot;\n };\n void changed().catch(report);\n\n const statusOf = (providerId: string) => current?.providers.find((entry) => entry.id === providerId);\n const refresh: PoProviderAuth['refresh'] = async (refreshOptions) => {\n const result = await models.refresh({ allowNetwork: refreshOptions?.allowNetwork ?? true });\n await changed();\n const snapshot = await settled();\n return {\n version: snapshot.version,\n errors: Object.fromEntries([...result.errors].map(([providerId, error]) => [providerId, error.message])),\n };\n };\n\n const service: PoProviderAuth = {\n abiVersion: 1,\n resolveAuth: async (providerId) => {\n const auth = await models.getAuth(providerId);\n return { version: (await settled()).version, ...(auth === undefined ? {} : { auth }) };\n },\n snapshot: async (sinceVersion) => {\n const snapshot = await settled();\n return sinceVersion === snapshot.version ? { version: snapshot.version } : snapshot;\n },\n refresh,\n };\n\n const authRuntime: AuthRuntime = {\n getProviders: () => models.getProviders(),\n getProviderAuthStatus: (providerId) => {\n const status = statusOf(providerId);\n return status === undefined ? { configured: false } : { configured: true, source: status.source };\n },\n getAvailableSnapshot: () => (current?.available ?? []).filter((model) => model.type === 'chat'),\n hasConfiguredAuth: (providerId) => statusOf(providerId) !== undefined,\n isUsingOAuth: (providerId) => statusOf(providerId)?.type === 'oauth',\n refresh: (refreshOptions) => refresh(refreshOptions),\n login: async (providerId, type, interaction) => {\n await models.login(providerId, type, interaction as AuthInteraction);\n await settled();\n },\n logout: async (providerId, logoutOptions) => {\n await models.logout(providerId, logoutOptions);\n await settled();\n },\n };\n\n return {\n service,\n authRuntime,\n setCustomProviders: async (definitions) => {\n applyCustomProviders(definitions);\n await changed();\n },\n };\n}\n\n/** Session credentials stay empty: every request's auth comes from the owner and is never kept. */\nconst NO_SESSION_CREDENTIALS: CredentialStore = {\n read: async () => undefined,\n list: async () => [],\n modify: async () => {\n throw new ModelsError('auth', MANAGED_BY_ADMINS);\n },\n delete: async () => {\n throw new ModelsError('auth', MANAGED_BY_ADMINS);\n },\n};\n\n/**\n * The session side: pi-ai `Models` with the same providers as the owner, whose auth asks the owner and\n * whose status and availability come from the owner's snapshot. Requests, streaming and header merges\n * run locally, unchanged; the session never holds a credential.\n */\nexport async function createOwnerBackedModelRuntime(options: OwnerBackedModelRuntimeOptions): Promise<PoModelRuntime> {\n const { owner } = options;\n let snapshot: PoProviderAuthSnapshot | undefined;\n let availableKeys = new Set<string>();\n const statusOf = (providerId: string) => snapshot?.providers.find((entry) => entry.id === providerId);\n const isAvailable = (model: AnyModel): boolean =>\n availableKeys.has(availableKey({ provider: model.provider, id: model.id, type: getModelType(model) }));\n\n const modelsStore: ModelsStore = {\n read: async (providerId) => snapshot?.models[providerId],\n // The owner persists catalogs; a session only restores the snapshot's copy.\n write: async () => undefined,\n delete: async () => undefined,\n };\n const models = createModels({\n credentials: NO_SESSION_CREDENTIALS,\n modelsStore,\n authContext: NO_AMBIENT_AUTH,\n });\n\n // A failed sync keeps the last snapshot; the next version the owner reports retries it.\n const report = options.onError ?? ((): void => undefined);\n let syncing: Promise<void> = Promise.resolve();\n const pull = async (): Promise<void> => {\n const next = await owner.snapshot(snapshot?.version);\n if (!('providers' in next)) return;\n const previousCustom = snapshot?.customProviders ?? [];\n snapshot = next;\n availableKeys = new Set(next.available.map(availableKey));\n for (const previous of previousCustom)\n if (!next.customProviders.some((definition) => definition.id === previous.id)) models.deleteProvider(previous.id);\n for (const definition of next.customProviders) models.setProvider(ownerBacked(customProvider(definition)));\n await models.refresh({ allowNetwork: false });\n };\n const sync = (): Promise<void> => {\n syncing = syncing.then(pull, pull);\n return syncing;\n };\n const observe = (version: number): void => {\n if (version !== snapshot?.version) void sync().catch(report);\n };\n\n const ownerBacked = (provider: Provider): Provider => {\n const apiKey = provider.auth.apiKey;\n return {\n ...provider,\n auth: {\n ...provider.auth,\n apiKey: {\n name: apiKey?.name ?? provider.name,\n check: async () => {\n const status = statusOf(provider.id);\n return status === undefined\n ? undefined\n : { type: status.type, ...(status.source === undefined ? {} : { source: status.source }) };\n },\n resolve: async ({ credential, signal }) => {\n // A key the caller passed for this request resolves locally, as it would on a machine.\n if (credential?.key !== undefined && apiKey !== undefined)\n return apiKey.resolve({ ctx: NO_AMBIENT_AUTH, credential, signal });\n const result = await owner.resolveAuth(provider.id);\n observe(result.version);\n return result.auth;\n },\n },\n },\n filterModels: (list) => list.filter(isAvailable),\n filterAllModels: (list) => list.filter(isAvailable),\n } as Provider;\n };\n for (const provider of (options.providers ?? builtinProviders)()) models.setProvider(ownerBacked(provider));\n await sync();\n\n const managedByAdmins = async (): Promise<never> => {\n throw new ModelsError('auth', MANAGED_BY_ADMINS);\n };\n const refused = (): never => {\n throw new ModelsError('provider', MANAGED_BY_ADMINS);\n };\n return {\n getProviders: models.getProviders.bind(models),\n getProvider: models.getProvider.bind(models),\n getModels: models.getModels.bind(models),\n getModel: models.getModel.bind(models),\n getModelsOfType: models.getModelsOfType.bind(models),\n getModelOfType: models.getModelOfType.bind(models),\n getAllModels: models.getAllModels.bind(models),\n checkAuth: models.checkAuth.bind(models),\n getAvailable: models.getAvailable.bind(models),\n getAvailableOfType: models.getAvailableOfType.bind(models),\n getAllAvailable: models.getAllAvailable.bind(models),\n getAuth: models.getAuth.bind(models),\n stream: models.stream.bind(models),\n complete: models.complete.bind(models),\n streamSimple: models.streamSimple.bind(models),\n completeSimple: models.completeSimple.bind(models),\n streamDeferred: models.streamDeferred.bind(models),\n fetchDeferred: models.fetchDeferred.bind(models),\n cancelDeferred: models.cancelDeferred.bind(models),\n generateImages: models.generateImages.bind(models),\n classify: models.classify.bind(models),\n refresh: async (refreshOptions?: { allowNetwork?: boolean }) => {\n const result = await owner.refresh({ allowNetwork: refreshOptions?.allowNetwork ?? true });\n if (result.version !== snapshot?.version) await sync();\n return {\n aborted: false,\n errors: new Map(\n Object.entries(result.errors).map(([providerId, message]) => [\n providerId,\n new ModelsError('model_source', message),\n ]),\n ),\n };\n },\n getAvailableSnapshot: () => models.getModels().filter(isAvailable),\n getError: () => undefined,\n hasConfiguredAuth: (providerId: string) => statusOf(providerId) !== undefined,\n isUsingOAuth: (providerId: string) => statusOf(providerId)?.type === 'oauth',\n isUsingSubscription: (providerId: string) => statusOf(providerId)?.subscription === true,\n getProviderAuthStatus: (providerId: string) => {\n const status = statusOf(providerId);\n return status === undefined ? { configured: false } : { configured: true, source: status.source };\n },\n login: managedByAdmins,\n logout: managedByAdmins,\n setRuntimeApiKey: managedByAdmins,\n removeRuntimeApiKey: managedByAdmins,\n listCredentials: async () => [],\n getRegisteredNativeProvider: () => undefined,\n getRegisteredProviderConfig: () => undefined,\n getRegisteredProviderIds: () => [],\n getCompatibilityRequestConfig: refused,\n registerProvider: refused,\n registerNativeProvider: refused,\n unregisterProvider: refused,\n registerVirtualModel: refused,\n unregisterVirtualModel: refused,\n } as unknown as PoModelRuntime;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/services/edgeModelRuntime/index.ts"],"mappings":";;;;;;wBAsCgB,eAAe,YAAY,6BAA6B;;wBAuCxD,oBAAoB,OAAO,mBAAmB;;;;;;wBAiB9C,wBAAwB,SAAS,2BAA2B;;;;;;wBA4JtD,8BAA8B,SAAS,iCAAiC,QAAQ"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/services/edgeModelRuntime/index.ts"],"mappings":";;;;;;wBAsCgB,eAAe,YAAY,6BAA6B;;wBAuCxD,oBAAoB,OAAO,mBAAmB;;;;;;wBAiB9C,wBAAwB,SAAS,2BAA2B;;;;;;wBA6JtD,8BAA8B,SAAS,iCAAiC,QAAQ"}
@@ -80,20 +80,21 @@ function createProviderAuthOwner(options) {
80
80
  pending = pending.then(recompute, recompute);
81
81
  return pending;
82
82
  };
83
- const models = createModels({
84
- credentials: {
85
- read: (providerId, operation) => options.credentials.read(providerId, operation),
86
- list: (operation) => options.credentials.list(operation),
87
- modify: async (providerId, fn, operation) => {
88
- const result = await options.credentials.modify(providerId, fn, operation);
89
- changed().catch(report);
90
- return result;
91
- },
92
- delete: async (providerId, operation) => {
93
- await options.credentials.delete(providerId, operation);
94
- changed().catch(report);
95
- }
83
+ const credentials = {
84
+ read: (providerId, operation) => options.credentials.read(providerId, operation),
85
+ list: (operation) => options.credentials.list(operation),
86
+ modify: async (providerId, fn, operation) => {
87
+ const result = await options.credentials.modify(providerId, fn, operation);
88
+ changed().catch(report);
89
+ return result;
96
90
  },
91
+ delete: async (providerId, operation) => {
92
+ await options.credentials.delete(providerId, operation);
93
+ changed().catch(report);
94
+ }
95
+ };
96
+ const models = createModels({
97
+ credentials,
97
98
  modelsStore: options.modelsStore,
98
99
  authContext: NO_AMBIENT_AUTH
99
100
  });
@@ -110,6 +111,7 @@ function createProviderAuthOwner(options) {
110
111
  let current;
111
112
  async function recompute() {
112
113
  const providers = models.getProviders();
114
+ const stored = new Map((await credentials.list()).map(({ providerId, type }) => [providerId, type]));
113
115
  const checks = await Promise.all(providers.map(async (provider) => ({
114
116
  provider,
115
117
  check: await models.checkAuth(provider.id).catch(() => void 0)
@@ -124,7 +126,7 @@ function createProviderAuthOwner(options) {
124
126
  providers: checks.flatMap(({ provider, check }) => check === void 0 ? [] : [{
125
127
  id: provider.id,
126
128
  type: check.type,
127
- ...check.source === void 0 ? {} : { source: check.source },
129
+ source: stored.get(provider.id) === check.type ? "stored" : check.source,
128
130
  subscription: check.type === "oauth" && provider.auth.oauth?.isSubscription === true
129
131
  }]),
130
132
  available: (await models.getAllAvailable()).map((model) => ({