@tangle-network/agent-app 0.60.192 → 0.60.193

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 (32) hide show
  1. package/dist/app-auth/index.js +3 -3
  2. package/dist/assistant/index.js +3 -3
  3. package/dist/chat-routes/index.js +7 -4
  4. package/dist/chat-routes/index.js.map +1 -1
  5. package/dist/chat-routes/model-failover-stream.d.ts +8 -0
  6. package/dist/chat-routes/sandbox-producer.d.ts +12 -0
  7. package/dist/chat-routes/turn-routes.d.ts +14 -0
  8. package/dist/{chunk-QAF7ZTO3.js → chunk-L5JLOC2F.js} +4 -4
  9. package/dist/chunk-XELWWXJJ.js +101 -0
  10. package/dist/chunk-XELWWXJJ.js.map +1 -0
  11. package/dist/{chunk-Q2H5FKVG.js → chunk-XP77DJEF.js} +16 -1
  12. package/dist/chunk-XP77DJEF.js.map +1 -0
  13. package/dist/{chunk-63EKFZOE.js → chunk-ZLX4BA27.js} +15 -1
  14. package/dist/chunk-ZLX4BA27.js.map +1 -0
  15. package/dist/model-resolution/failover.d.ts +16 -2
  16. package/dist/model-resolution/index.d.ts +42 -0
  17. package/dist/model-resolution/index.js +30 -5
  18. package/dist/model-resolution/index.js.map +1 -1
  19. package/dist/policy-filters/index.d.ts +166 -0
  20. package/dist/policy-filters/index.js +23 -0
  21. package/dist/policy-filters/index.js.map +1 -0
  22. package/dist/public-consultation/index.d.ts +1 -1
  23. package/dist/public-consultation/index.js +2 -1
  24. package/dist/public-consultation/index.js.map +1 -1
  25. package/dist/sandbox/index.js +2 -2
  26. package/dist/settings-react/index.js +1 -1
  27. package/dist/tools/index.js +2 -2
  28. package/dist/web-react/index.js +7 -7
  29. package/package.json +6 -1
  30. package/dist/chunk-63EKFZOE.js.map +0 -1
  31. package/dist/chunk-Q2H5FKVG.js.map +0 -1
  32. /package/dist/{chunk-QAF7ZTO3.js.map → chunk-L5JLOC2F.js.map} +0 -0
@@ -6,7 +6,10 @@ import {
6
6
  isUpstreamUnavailable,
7
7
  readHttpStatusHint,
8
8
  runWithModelFailover
9
- } from "../chunk-63EKFZOE.js";
9
+ } from "../chunk-ZLX4BA27.js";
10
+ import {
11
+ checkModelPolicy
12
+ } from "../chunk-XELWWXJJ.js";
10
13
 
11
14
  // src/model-resolution/allow-list.ts
12
15
  function parseModelNotAllowed(message) {
@@ -49,12 +52,18 @@ function resolveChatModel(input) {
49
52
  return { model: input.defaultModel, source: "default" };
50
53
  }
51
54
  async function validateChatModelId(modelId, input) {
55
+ const admit = async (value) => {
56
+ if (!input.policy) return { succeeded: true, value };
57
+ const decision = await checkModelPolicy(value, input.policy.catalog, input.policy.filter, input.policy.context);
58
+ if (!decision.allowed) return { succeeded: false, error: decision.reason };
59
+ return { succeeded: true, value };
60
+ };
52
61
  const cleaned = cleanModelId(modelId);
53
62
  if (!cleaned) return { succeeded: false, error: "Model id must be a non-empty string." };
54
63
  if (!isWellFormedModelId(cleaned)) return { succeeded: false, error: `Model id is malformed: ${cleaned}` };
55
64
  const allowed = new Set(input.allowlist ?? []);
56
- if (allowed.has(cleaned)) return { succeeded: true, value: cleaned };
57
- if (cleanModelId(input.envModel) === cleaned) return { succeeded: true, value: cleaned };
65
+ if (allowed.has(cleaned)) return admit(cleaned);
66
+ if (cleanModelId(input.envModel) === cleaned) return admit(cleaned);
58
67
  if (!input.loadModels || typeof input.routerBaseUrl !== "string" || input.routerBaseUrl.length === 0) {
59
68
  return { succeeded: false, error: `Model is not available: ${cleaned}` };
60
69
  }
@@ -66,7 +75,7 @@ async function validateChatModelId(modelId, input) {
66
75
  return { succeeded: false, error: `Could not validate model catalog: ${message}` };
67
76
  }
68
77
  const ids = new Set(catalog.flatMap(catalogIdsForModel));
69
- if (ids.has(cleaned)) return { succeeded: true, value: cleaned };
78
+ if (ids.has(cleaned)) return admit(cleaned);
70
79
  if (!cleaned.includes("/")) {
71
80
  const canonicalBySuffix = /* @__PURE__ */ new Map();
72
81
  for (const model of catalog) {
@@ -79,7 +88,7 @@ async function validateChatModelId(modelId, input) {
79
88
  else canonicalBySuffix.set(suffix, [canonical]);
80
89
  }
81
90
  const matches = canonicalBySuffix.get(cleaned);
82
- if (matches && matches.length === 1) return { succeeded: true, value: matches[0] };
91
+ if (matches && matches.length === 1) return admit(matches[0]);
83
92
  }
84
93
  return { succeeded: false, error: `Model is not available: ${cleaned}` };
85
94
  }
@@ -101,6 +110,21 @@ function catalogIdsForModel(model) {
101
110
  }
102
111
  return [...ids];
103
112
  }
113
+ function modelPolicyCatalogFromRouter(rows) {
114
+ const catalog = [];
115
+ for (const row of rows) {
116
+ if (typeof row.id !== "string" || !row.id.trim()) continue;
117
+ const provider = row._provider ?? row.provider;
118
+ const flags = {
119
+ ...typeof row.zdr === "boolean" ? { zeroDataRetention: row.zdr } : {},
120
+ ...typeof row.hipaa_eligible === "boolean" ? { hipaaEligible: row.hipaa_eligible } : {}
121
+ };
122
+ for (const id of catalogIdsForModel(row)) {
123
+ catalog.push({ id, ...provider ? { provider } : {}, ...flags });
124
+ }
125
+ }
126
+ return catalog;
127
+ }
104
128
  export {
105
129
  ModelFailoverExhaustedError,
106
130
  UPSTREAM_UNAVAILABLE_CODES,
@@ -111,6 +135,7 @@ export {
111
135
  formatModelNotAllowedRefusal,
112
136
  isUpstreamUnavailable,
113
137
  isWellFormedModelId,
138
+ modelPolicyCatalogFromRouter,
114
139
  parseModelNotAllowed,
115
140
  readHttpStatusHint,
116
141
  resolveChatModel,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/model-resolution/allow-list.ts","../../src/model-resolution/index.ts"],"sourcesContent":["/**\n * Live model routing by allow-list — the positive form of `model_not_allowed`.\n *\n * A scoped key (QA keys, delegated child keys, provisioned product keys)\n * carries an allowed-model set. The router enforces it as a wall:\n *\n * 400 model_not_allowed — `Model \"gpt-6-luna\" is not in this key's\n * allow-list. Allowed: glm-5.3, gpt-5.5.`\n *\n * The refusal already NAMES the allowed set — so a product can treat the\n * allow-list as routing information instead of a dead end: parse the set,\n * pick the first preferred model it permits, and retry. Combined with the\n * router's key-info endpoint (which exposes `allowed_models` before the\n * first refusal), a product can route right the first time.\n *\n * Fail-closed by design: when no preferred model is allowed, the resolver\n * returns `status: 'refused'` with the allowed set — the caller surfaces a\n * named refusal (via {@link formatModelNotAllowedRefusal}) rather than\n * silently downgrading to an arbitrary model. A different model is a\n * different decision-maker; the product's fallback ORDER is a product\n * decision, and this module only executes it.\n */\n\n/** Parse the allowed-model set out of a router `model_not_allowed` refusal.\n * Both refusal shapes name the set after `Allowed:` —\n * `Model \"x\" is not in this key's allow-list. Allowed: a, b, c.` and\n * `Fallback model(s) \"x\" are not in this key's allow-list. Allowed: a, b.`\n * Returns null when the message carries no set (an unexpected shape — do\n * not guess). */\nexport function parseModelNotAllowed(message: string): string[] | null {\n const match = /Allowed:\\s*([^\\n]+?)(?:\\.\\s*)?$/.exec(message)\n if (!match?.[1]) return null\n const allowed = match[1].split(',').map((id) => id.trim().replace(/^[\"']|[\"']$/g, '')).filter(Boolean)\n return allowed.length > 0 ? allowed : null\n}\n\nexport interface ResolveModelWithinAllowListInput {\n /** The model the product wants to run. */\n readonly requested: string\n /** The key's allowed-model set (empty/absent = the key is unscoped; the\n * request stands). */\n readonly allowedModels?: readonly string[] | null\n /** The product's reroute order when `requested` is blocked — tried in\n * order, first allowed wins. */\n readonly fallbacks?: readonly string[]\n}\n\nexport type ModelWithinAllowListResolution =\n | { readonly status: 'allowed'; readonly model: string; readonly rerouted: false }\n | { readonly status: 'rerouted'; readonly model: string; readonly rerouted: true; readonly blockedModel: string }\n | { readonly status: 'refused'; readonly model: null; readonly allowedModels: readonly string[] }\n\n/** Resolve a model request against the key's allow-list:\n * unscoped → allowed as-is; requested permitted → allowed; a fallback\n * permitted → rerouted (named); nothing permitted → refused with the set. */\nexport function resolveModelWithinAllowList(input: ResolveModelWithinAllowListInput): ModelWithinAllowListResolution {\n const allowed = input.allowedModels\n if (!allowed || allowed.length === 0) {\n return { status: 'allowed', model: input.requested, rerouted: false }\n }\n if (allowed.includes(input.requested)) {\n return { status: 'allowed', model: input.requested, rerouted: false }\n }\n const fallback = (input.fallbacks ?? []).find((id) => allowed.includes(id))\n if (fallback !== undefined) {\n return { status: 'rerouted', model: fallback, rerouted: true, blockedModel: input.requested }\n }\n return { status: 'refused', model: null, allowedModels: allowed }\n}\n\n/** Format the loud, correctable refusal: the blocked model, the allowed set,\n * and the fix. Mirrors the router's own refusal text so both layers name\n * the same fact. */\nexport function formatModelNotAllowedRefusal(input: {\n requested: string\n allowedModels: readonly string[]\n}): string {\n return (\n `Model \"${input.requested}\" is not in this key's allow-list. ` +\n `Allowed: ${input.allowedModels.join(', ')}. ` +\n 'Route to an allowed model or widen the key\\u2019s allow-list.'\n )\n}\n","/**\n * Canonical chat-model resolution — identical across every agent app.\n *\n * The ONLY per-app inputs are DATA, never logic: the default model, the\n * allowlist, the env value the deployment set, and the catalog-fetch loader.\n * The logic is one precedence ladder + one fail-closed validator that every\n * product uses the same way — there is no per-product variant, no env-var name\n * baked in, and no backend dimension (router-vs-sandbox is the harness/dispatch\n * concern, not model resolution; a sandbox's provider default lives in the\n * sandbox subpath).\n *\n * - resolveChatModel: request > workspace > env > default. The product reads its\n * own deploy env var and passes the VALUE as `envModel`; the shell knows no\n * env-var names. Source is canonical: 'request' | 'workspace' | 'env' | 'default'.\n * - validateChatModelId: fail-closed. Admit an id that is in the allowlist, or\n * equals the operator-set env model, or is served by the live router catalog\n * (exact, or a bare id resolved to its canonical id when the suffix is unique).\n */\n\n/** The router /v1/models entry shape this module reads. Minimal on purpose. */\nexport interface ModelInfo {\n id: string\n name?: string\n _provider?: string\n provider?: string\n}\n\n/** Canonical (provider-prefixed) id for a catalog entry: pass through an id that\n * already carries a provider, else prefix the entry's provider when present. */\nfunction canonicalModelId(model: ModelInfo): string {\n if (model.id.includes('/')) return model.id\n const provider = model._provider ?? model.provider\n return provider ? `${provider}/${model.id}` : model.id\n}\n\n/** Define possible origins for the chat model configuration values */\nexport type ChatModelSource = 'request' | 'workspace' | 'env' | 'default'\n\n/** Resolve a chat model with its identifier and source information */\nexport interface ResolvedChatModel {\n model: string\n source: ChatModelSource\n}\n\n/** Represent successful chat model validation with a true status and a validated string value */\nexport interface ChatModelValidationSuccess {\n succeeded: true\n value: string\n}\n\n/** Describe a failed chat model validation result with an error message */\nexport interface ChatModelValidationFailure {\n succeeded: false\n error: string\n}\n\n/** Resolve the outcome of validating a chat model as either success or failure */\nexport type ChatModelValidationResult = ChatModelValidationSuccess | ChatModelValidationFailure\n\n/** The catalog-fetch boundary: maps a router base URL to the raw model list. */\nexport type LoadModels = (routerBaseUrl: string) => Promise<ModelInfo[]>\n\n/** Resolve the effective chat model input by prioritizing request, workspace, environment, and default models */\nexport interface ResolveChatModelInput {\n /** Per-request override (highest precedence). */\n requestModel?: string\n /** Persisted workspace-pinned model. */\n workspaceModel?: string\n /** The value the deployment's model env var holds (the product reads its own\n * var name and passes the value — the shell stays env-var-name agnostic). */\n envModel?: string\n /** Final fallback (the product's default, typically profile.model.default). */\n defaultModel: string\n}\n\n/** Resolve the chat-turn model by the one canonical precedence. Blank values are\n * treated as absent. */\nexport function resolveChatModel(input: ResolveChatModelInput): ResolvedChatModel {\n const request = cleanModelId(input.requestModel)\n if (request) return { model: request, source: 'request' }\n const workspace = cleanModelId(input.workspaceModel)\n if (workspace) return { model: workspace, source: 'workspace' }\n const env = cleanModelId(input.envModel)\n if (env) return { model: env, source: 'env' }\n return { model: input.defaultModel, source: 'default' }\n}\n\n/** Define input parameters for validating chat model IDs with optional allowlist and catalog access details */\nexport interface ValidateChatModelIdInput {\n /** Ids accepted without a catalog round-trip (defaults + operator-trusted). */\n allowlist?: Iterable<string>\n /** The operator-set env model value — always admitted (operator-trusted). */\n envModel?: string\n /** Catalog loader; required to reach the catalog path. */\n loadModels?: LoadModels\n /** Catalog endpoint base; required to reach the catalog path. */\n routerBaseUrl?: string\n}\n\n/**\n * Fail-closed model-id validation. Accepts an id only when it is well-formed AND\n * (in the allowlist, or equals the operator-set env model, or served by the live\n * catalog). A bare id (no provider prefix) resolves to its canonical id only when\n * the suffix is unique across the catalog — an ambiguous suffix is rejected\n * rather than silently assigned a provider.\n */\nexport async function validateChatModelId(\n modelId: unknown,\n input: ValidateChatModelIdInput,\n): Promise<ChatModelValidationResult> {\n const cleaned = cleanModelId(modelId)\n if (!cleaned) return { succeeded: false, error: 'Model id must be a non-empty string.' }\n if (!isWellFormedModelId(cleaned)) return { succeeded: false, error: `Model id is malformed: ${cleaned}` }\n\n const allowed = new Set(input.allowlist ?? [])\n if (allowed.has(cleaned)) return { succeeded: true, value: cleaned }\n\n // The operator-set env model is trusted without a catalog round-trip.\n if (cleanModelId(input.envModel) === cleaned) return { succeeded: true, value: cleaned }\n\n if (!input.loadModels || typeof input.routerBaseUrl !== 'string' || input.routerBaseUrl.length === 0) {\n return { succeeded: false, error: `Model is not available: ${cleaned}` }\n }\n\n let catalog: ModelInfo[]\n try {\n catalog = await input.loadModels(input.routerBaseUrl)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n return { succeeded: false, error: `Could not validate model catalog: ${message}` }\n }\n\n const ids = new Set(catalog.flatMap(catalogIdsForModel))\n if (ids.has(cleaned)) return { succeeded: true, value: cleaned }\n\n if (!cleaned.includes('/')) {\n const canonicalBySuffix = new Map<string, string[]>()\n for (const model of catalog) {\n if (typeof model.id !== 'string' || !model.id.trim()) continue\n const canonical = canonicalModelId(model)\n if (!canonical.includes('/')) continue\n const suffix = canonical.split('/').slice(1).join('/')\n const entries = canonicalBySuffix.get(suffix)\n if (entries) entries.push(canonical)\n else canonicalBySuffix.set(suffix, [canonical])\n }\n const matches = canonicalBySuffix.get(cleaned)\n if (matches && matches.length === 1) return { succeeded: true, value: matches[0]! }\n }\n\n return { succeeded: false, error: `Model is not available: ${cleaned}` }\n}\n\n/** Resolve and return a trimmed string model ID or undefined for invalid or empty input */\nexport function cleanModelId(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\n/** Validate if a model ID string conforms to length and character format requirements */\nexport function isWellFormedModelId(modelId: string): boolean {\n if (modelId.length > 200) return false\n return /^[A-Za-z0-9._/@:-]+$/.test(modelId)\n}\n\n/** Resolve unique catalog IDs associated with a given model including its canonical form if applicable */\nexport function catalogIdsForModel(model: ModelInfo): string[] {\n const ids = new Set<string>()\n if (typeof model.id === 'string' && model.id.trim()) ids.add(model.id.trim())\n if (typeof model.id === 'string' && model.id.trim() && !model.id.includes('/')) {\n const canonical = canonicalModelId(model)\n if (canonical.includes('/')) ids.add(canonical)\n }\n return [...ids]\n}\n\n// Live model routing by allow-list: the positive form of model_not_allowed.\n// Parse the set the router's refusal already names, reroute within the\n// product's fallback order, or refuse loudly naming the set.\nexport {\n parseModelNotAllowed,\n resolveModelWithinAllowList,\n formatModelNotAllowedRefusal,\n type ResolveModelWithinAllowListInput,\n type ModelWithinAllowListResolution,\n} from './allow-list'\n\n// Reactive failover for the case validation cannot catch: a model that IS in the\n// catalog but whose upstream is down. Catalog membership is not liveness.\nexport {\n isUpstreamUnavailable,\n readHttpStatusHint,\n runWithModelFailover,\n buildModelChain,\n ModelFailoverExhaustedError,\n UPSTREAM_UNAVAILABLE_CODES,\n UPSTREAM_UNAVAILABLE_STATUSES,\n type ModelFailoverAttempt,\n type ModelFailoverResult,\n type RunWithModelFailoverInput,\n} from './failover'\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/model-resolution/allow-list.ts","../../src/model-resolution/index.ts"],"sourcesContent":["/**\n * Live model routing by allow-list — the positive form of `model_not_allowed`.\n *\n * A scoped key (QA keys, delegated child keys, provisioned product keys)\n * carries an allowed-model set. The router enforces it as a wall:\n *\n * 400 model_not_allowed — `Model \"gpt-6-luna\" is not in this key's\n * allow-list. Allowed: glm-5.3, gpt-5.5.`\n *\n * The refusal already NAMES the allowed set — so a product can treat the\n * allow-list as routing information instead of a dead end: parse the set,\n * pick the first preferred model it permits, and retry. Combined with the\n * router's key-info endpoint (which exposes `allowed_models` before the\n * first refusal), a product can route right the first time.\n *\n * Fail-closed by design: when no preferred model is allowed, the resolver\n * returns `status: 'refused'` with the allowed set — the caller surfaces a\n * named refusal (via {@link formatModelNotAllowedRefusal}) rather than\n * silently downgrading to an arbitrary model. A different model is a\n * different decision-maker; the product's fallback ORDER is a product\n * decision, and this module only executes it.\n */\n\n/** Parse the allowed-model set out of a router `model_not_allowed` refusal.\n * Both refusal shapes name the set after `Allowed:` —\n * `Model \"x\" is not in this key's allow-list. Allowed: a, b, c.` and\n * `Fallback model(s) \"x\" are not in this key's allow-list. Allowed: a, b.`\n * Returns null when the message carries no set (an unexpected shape — do\n * not guess). */\nexport function parseModelNotAllowed(message: string): string[] | null {\n const match = /Allowed:\\s*([^\\n]+?)(?:\\.\\s*)?$/.exec(message)\n if (!match?.[1]) return null\n const allowed = match[1].split(',').map((id) => id.trim().replace(/^[\"']|[\"']$/g, '')).filter(Boolean)\n return allowed.length > 0 ? allowed : null\n}\n\nexport interface ResolveModelWithinAllowListInput {\n /** The model the product wants to run. */\n readonly requested: string\n /** The key's allowed-model set (empty/absent = the key is unscoped; the\n * request stands). */\n readonly allowedModels?: readonly string[] | null\n /** The product's reroute order when `requested` is blocked — tried in\n * order, first allowed wins. */\n readonly fallbacks?: readonly string[]\n}\n\nexport type ModelWithinAllowListResolution =\n | { readonly status: 'allowed'; readonly model: string; readonly rerouted: false }\n | { readonly status: 'rerouted'; readonly model: string; readonly rerouted: true; readonly blockedModel: string }\n | { readonly status: 'refused'; readonly model: null; readonly allowedModels: readonly string[] }\n\n/** Resolve a model request against the key's allow-list:\n * unscoped → allowed as-is; requested permitted → allowed; a fallback\n * permitted → rerouted (named); nothing permitted → refused with the set. */\nexport function resolveModelWithinAllowList(input: ResolveModelWithinAllowListInput): ModelWithinAllowListResolution {\n const allowed = input.allowedModels\n if (!allowed || allowed.length === 0) {\n return { status: 'allowed', model: input.requested, rerouted: false }\n }\n if (allowed.includes(input.requested)) {\n return { status: 'allowed', model: input.requested, rerouted: false }\n }\n const fallback = (input.fallbacks ?? []).find((id) => allowed.includes(id))\n if (fallback !== undefined) {\n return { status: 'rerouted', model: fallback, rerouted: true, blockedModel: input.requested }\n }\n return { status: 'refused', model: null, allowedModels: allowed }\n}\n\n/** Format the loud, correctable refusal: the blocked model, the allowed set,\n * and the fix. Mirrors the router's own refusal text so both layers name\n * the same fact. */\nexport function formatModelNotAllowedRefusal(input: {\n requested: string\n allowedModels: readonly string[]\n}): string {\n return (\n `Model \"${input.requested}\" is not in this key's allow-list. ` +\n `Allowed: ${input.allowedModels.join(', ')}. ` +\n 'Route to an allowed model or widen the key\\u2019s allow-list.'\n )\n}\n","/**\n * Canonical chat-model resolution — identical across every agent app.\n *\n * The ONLY per-app inputs are DATA, never logic: the default model, the\n * allowlist, the env value the deployment set, and the catalog-fetch loader.\n * The logic is one precedence ladder + one fail-closed validator that every\n * product uses the same way — there is no per-product variant, no env-var name\n * baked in, and no backend dimension (router-vs-sandbox is the harness/dispatch\n * concern, not model resolution; a sandbox's provider default lives in the\n * sandbox subpath).\n *\n * - resolveChatModel: request > workspace > env > default. The product reads its\n * own deploy env var and passes the VALUE as `envModel`; the shell knows no\n * env-var names. Source is canonical: 'request' | 'workspace' | 'env' | 'default'.\n * - validateChatModelId: fail-closed. Admit an id that is in the allowlist, or\n * equals the operator-set env model, or is served by the live router catalog\n * (exact, or a bare id resolved to its canonical id when the suffix is unique).\n * An optional `policy` (see ../policy-filters) layers product admission policy\n * over EVERY admission path — allowlist, env, and catalog alike — so a\n * policy-filtered deployment (e.g. HIPAA: zero-data-retention, BAA-eligible\n * models only) cannot be bypassed through an operator-trusted lane. With a\n * policy configured, a model carrying no policy metadata is refused.\n */\n\nimport {\n checkModelPolicy,\n type ModelPolicy,\n type ModelPolicyInfo,\n} from '../policy-filters/index'\n\n/** The router /v1/models entry shape this module reads. Minimal on purpose. */\nexport interface ModelInfo {\n id: string\n name?: string\n _provider?: string\n provider?: string\n}\n\n/** Canonical (provider-prefixed) id for a catalog entry: pass through an id that\n * already carries a provider, else prefix the entry's provider when present. */\nfunction canonicalModelId(model: ModelInfo): string {\n if (model.id.includes('/')) return model.id\n const provider = model._provider ?? model.provider\n return provider ? `${provider}/${model.id}` : model.id\n}\n\n/** Define possible origins for the chat model configuration values */\nexport type ChatModelSource = 'request' | 'workspace' | 'env' | 'default'\n\n/** Resolve a chat model with its identifier and source information */\nexport interface ResolvedChatModel {\n model: string\n source: ChatModelSource\n}\n\n/** Represent successful chat model validation with a true status and a validated string value */\nexport interface ChatModelValidationSuccess {\n succeeded: true\n value: string\n}\n\n/** Describe a failed chat model validation result with an error message */\nexport interface ChatModelValidationFailure {\n succeeded: false\n error: string\n}\n\n/** Resolve the outcome of validating a chat model as either success or failure */\nexport type ChatModelValidationResult = ChatModelValidationSuccess | ChatModelValidationFailure\n\n/** The catalog-fetch boundary: maps a router base URL to the raw model list. */\nexport type LoadModels = (routerBaseUrl: string) => Promise<ModelInfo[]>\n\n/** Resolve the effective chat model input by prioritizing request, workspace, environment, and default models */\nexport interface ResolveChatModelInput {\n /** Per-request override (highest precedence). */\n requestModel?: string\n /** Persisted workspace-pinned model. */\n workspaceModel?: string\n /** The value the deployment's model env var holds (the product reads its own\n * var name and passes the value — the shell stays env-var-name agnostic). */\n envModel?: string\n /** Final fallback (the product's default, typically profile.model.default). */\n defaultModel: string\n}\n\n/** Resolve the chat-turn model by the one canonical precedence. Blank values are\n * treated as absent. */\nexport function resolveChatModel(input: ResolveChatModelInput): ResolvedChatModel {\n const request = cleanModelId(input.requestModel)\n if (request) return { model: request, source: 'request' }\n const workspace = cleanModelId(input.workspaceModel)\n if (workspace) return { model: workspace, source: 'workspace' }\n const env = cleanModelId(input.envModel)\n if (env) return { model: env, source: 'env' }\n return { model: input.defaultModel, source: 'default' }\n}\n\n/** Define input parameters for validating chat model IDs with optional allowlist and catalog access details */\nexport interface ValidateChatModelIdInput {\n /** Ids accepted without a catalog round-trip (defaults + operator-trusted). */\n allowlist?: Iterable<string>\n /** The operator-set env model value — always admitted (operator-trusted). */\n envModel?: string\n /** Catalog loader; required to reach the catalog path. */\n loadModels?: LoadModels\n /** Catalog endpoint base; required to reach the catalog path. */\n routerBaseUrl?: string\n /** Product admission policy, evaluated AFTER availability on EVERY admission\n * path (allowlist, env, catalog). Fail-closed: a validated model with no\n * policy metadata in `catalog` is refused. */\n policy?: ModelPolicy\n}\n\n/**\n * Fail-closed model-id validation. Accepts an id only when it is well-formed AND\n * (in the allowlist, or equals the operator-set env model, or served by the live\n * catalog). A bare id (no provider prefix) resolves to its canonical id only when\n * the suffix is unique across the catalog — an ambiguous suffix is rejected\n * rather than silently assigned a provider.\n */\nexport async function validateChatModelId(\n modelId: unknown,\n input: ValidateChatModelIdInput,\n): Promise<ChatModelValidationResult> {\n // Policy composes with availability: an id is admitted only when it is\n // available AND (no policy configured OR the policy admits it). The policy\n // runs on every lane, so the operator-trusted allowlist/env paths cannot\n // bypass a deployment's admission policy.\n const admit = async (value: string): Promise<ChatModelValidationResult> => {\n if (!input.policy) return { succeeded: true, value }\n const decision = await checkModelPolicy(value, input.policy.catalog, input.policy.filter, input.policy.context)\n if (!decision.allowed) return { succeeded: false, error: decision.reason }\n return { succeeded: true, value }\n }\n\n const cleaned = cleanModelId(modelId)\n if (!cleaned) return { succeeded: false, error: 'Model id must be a non-empty string.' }\n if (!isWellFormedModelId(cleaned)) return { succeeded: false, error: `Model id is malformed: ${cleaned}` }\n\n const allowed = new Set(input.allowlist ?? [])\n if (allowed.has(cleaned)) return admit(cleaned)\n\n // The operator-set env model is trusted without a catalog round-trip.\n if (cleanModelId(input.envModel) === cleaned) return admit(cleaned)\n\n if (!input.loadModels || typeof input.routerBaseUrl !== 'string' || input.routerBaseUrl.length === 0) {\n return { succeeded: false, error: `Model is not available: ${cleaned}` }\n }\n\n let catalog: ModelInfo[]\n try {\n catalog = await input.loadModels(input.routerBaseUrl)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n return { succeeded: false, error: `Could not validate model catalog: ${message}` }\n }\n\n const ids = new Set(catalog.flatMap(catalogIdsForModel))\n if (ids.has(cleaned)) return admit(cleaned)\n\n if (!cleaned.includes('/')) {\n const canonicalBySuffix = new Map<string, string[]>()\n for (const model of catalog) {\n if (typeof model.id !== 'string' || !model.id.trim()) continue\n const canonical = canonicalModelId(model)\n if (!canonical.includes('/')) continue\n const suffix = canonical.split('/').slice(1).join('/')\n const entries = canonicalBySuffix.get(suffix)\n if (entries) entries.push(canonical)\n else canonicalBySuffix.set(suffix, [canonical])\n }\n const matches = canonicalBySuffix.get(cleaned)\n if (matches && matches.length === 1) return admit(matches[0]!)\n }\n\n return { succeeded: false, error: `Model is not available: ${cleaned}` }\n}\n\n/** Resolve and return a trimmed string model ID or undefined for invalid or empty input */\nexport function cleanModelId(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\n/** Validate if a model ID string conforms to length and character format requirements */\nexport function isWellFormedModelId(modelId: string): boolean {\n if (modelId.length > 200) return false\n return /^[A-Za-z0-9._/@:-]+$/.test(modelId)\n}\n\n/** Resolve unique catalog IDs associated with a given model including its canonical form if applicable */\nexport function catalogIdsForModel(model: ModelInfo): string[] {\n const ids = new Set<string>()\n if (typeof model.id === 'string' && model.id.trim()) ids.add(model.id.trim())\n if (typeof model.id === 'string' && model.id.trim() && !model.id.includes('/')) {\n const canonical = canonicalModelId(model)\n if (canonical.includes('/')) ids.add(canonical)\n }\n return [...ids]\n}\n\n/**\n * A router `/v1/models` row carrying the compliance flags\n * {@link modelPolicyCatalogFromRouter} reads. The router spells them\n * snake_case; the policy catalog spells them camelCase (`ModelPolicyInfo`).\n */\nexport interface RouterModelPolicyRow extends ModelInfo {\n /** The provider retains no prompt/response data for this model. */\n zdr?: boolean\n /** The vendor will sign a BAA covering this model. */\n hipaa_eligible?: boolean\n}\n\n/**\n * Map router `/v1/models` rows to a {@link ModelPolicyInfo} catalog: snake_case\n * `zdr` / `hipaa_eligible` become `zeroDataRetention` / `hipaaEligible`, and\n * `provider` comes from the row's `_provider`/`provider`.\n *\n * GRANULARITY: the flags are trusted VERBATIM, per row, at whatever\n * granularity the router publishes them. A ZDR/BAA attestation is commonly a\n * provider-wide vendor fact, but this mapper never infers one model's\n * eligibility from a sibling's — a row that omits a flag yields `undefined`\n * for it, and a filter reading `=== true` refuses that row (fail closed). If\n * your attestation is provider-wide, publish it on every row upstream rather\n * than asking the mapper to guess.\n *\n * ID KEYING: one entry is emitted per spelling {@link catalogIdsForModel}\n * yields (the bare id AND its provider-prefixed canonical form), so a turn\n * carrying either spelling classifies — lookups in `/policy-filters` are\n * exact-string and never normalize. Rows with a blank/non-string id are\n * skipped. Duplicate ids across rows are a product bug (see\n * `indexPolicyCatalog`).\n */\nexport function modelPolicyCatalogFromRouter(rows: Iterable<RouterModelPolicyRow>): ModelPolicyInfo[] {\n const catalog: ModelPolicyInfo[] = []\n for (const row of rows) {\n if (typeof row.id !== 'string' || !row.id.trim()) continue\n const provider = row._provider ?? row.provider\n const flags = {\n ...(typeof row.zdr === 'boolean' ? { zeroDataRetention: row.zdr } : {}),\n ...(typeof row.hipaa_eligible === 'boolean' ? { hipaaEligible: row.hipaa_eligible } : {}),\n }\n for (const id of catalogIdsForModel(row)) {\n catalog.push({ id, ...(provider ? { provider } : {}), ...flags })\n }\n }\n return catalog\n}\n\n// Live model routing by allow-list: the positive form of model_not_allowed.\n// Parse the set the router's refusal already names, reroute within the\n// product's fallback order, or refuse loudly naming the set.\nexport {\n parseModelNotAllowed,\n resolveModelWithinAllowList,\n formatModelNotAllowedRefusal,\n type ResolveModelWithinAllowListInput,\n type ModelWithinAllowListResolution,\n} from './allow-list'\n\n// Reactive failover for the case validation cannot catch: a model that IS in the\n// catalog but whose upstream is down. Catalog membership is not liveness.\nexport {\n isUpstreamUnavailable,\n readHttpStatusHint,\n runWithModelFailover,\n buildModelChain,\n ModelFailoverExhaustedError,\n UPSTREAM_UNAVAILABLE_CODES,\n UPSTREAM_UNAVAILABLE_STATUSES,\n type ModelFailoverAttempt,\n type ModelFailoverResult,\n type RunWithModelFailoverInput,\n} from './failover'\n"],"mappings":"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","names":[]}
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Policy filters — product-supplied admission policy over the models a
3
+ * deployment will run.
4
+ *
5
+ * The engine owns the MECHANISM (a typed predicate, combinators, a fail-closed
6
+ * enforcement seam, a catalog filter for pickers); the product owns the POLICY
7
+ * (the metadata and the filter), supplied as data. Nothing here names a vendor,
8
+ * a regulation, or a product — a HIPAA deployment's "zero-data-retention,
9
+ * BAA-eligible models only" rule is one `PolicyFilter` over product-supplied
10
+ * `ModelPolicyInfo`, and any other restriction composes the same way.
11
+ *
12
+ * Fail-closed contract, everywhere this module decides:
13
+ *
14
+ * - An item with NO policy metadata is refused (enforcement) or hidden
15
+ * (catalog filter). A policy that cannot classify an item does not admit it.
16
+ * - A filter that THROWS is a refusal, never an admission — a broken policy
17
+ * check fails the turn instead of waving it through.
18
+ * - Enforcement composes with availability, never replaces it: the
19
+ * `model-resolution` seam applies the policy AFTER its existing
20
+ * allowlist/env/catalog admission, on every path — a policy-filtered
21
+ * deployment cannot be bypassed through an operator-trusted lane.
22
+ *
23
+ * Browser-safe and dependency-free by construction: no imports at all, so UI
24
+ * pickers (docs/ui-picker-canon.md) can filter the catalog they pass to
25
+ * `ModelPicker` with the same filter the server enforces.
26
+ */
27
+ /** Arbitrary context a product threads to its filters (workspace, user, turn). */
28
+ export type PolicyFilterContext = Record<string, unknown>;
29
+ /**
30
+ * A typed admission predicate over one catalog item. May be async (a product
31
+ * can read its metadata from storage). Returning `false` denies; THROWING
32
+ * denies too — enforcement treats a filter error as a refusal.
33
+ */
34
+ export type PolicyFilter<T, C extends object = PolicyFilterContext> = (item: T, context: C) => boolean | Promise<boolean>;
35
+ /**
36
+ * Admit only items every filter admits.
37
+ *
38
+ * Throws at COMPOSITION time on an empty `allOf()`: an empty conjunction
39
+ * admits everything, which reads as a policy that was never wired — the same
40
+ * silent-admission class as a missing filter. Say "no restriction" out loud
41
+ * with {@link allowAll} instead.
42
+ */
43
+ export declare function allOf<T, C extends object = PolicyFilterContext>(...filters: Array<PolicyFilter<T, C>>): PolicyFilter<T, C>;
44
+ /** Admit items at least one filter admits. An empty `anyOf` admits nothing —
45
+ * already fail-closed, so it is allowed. */
46
+ export declare function anyOf<T, C extends object = PolicyFilterContext>(...filters: Array<PolicyFilter<T, C>>): PolicyFilter<T, C>;
47
+ /**
48
+ * The explicit "no restriction" filter — what an empty `allOf()` used to mean
49
+ * silently. Admits every item; the catalog still must classify the id (a
50
+ * policy of `allowAll` over an empty catalog admits nothing).
51
+ */
52
+ export declare function allowAll<T, C extends object = PolicyFilterContext>(): PolicyFilter<T, C>;
53
+ /**
54
+ * Policy metadata for one model — DATA the product supplies (from its router
55
+ * catalog, a compliance review, a vendor attestation), never engine policy.
56
+ * The named fields are the common compliance dimensions; the index signature
57
+ * carries any product-specific attribute a filter reads.
58
+ */
59
+ export interface ModelPolicyInfo {
60
+ id: string;
61
+ provider?: string;
62
+ /** The provider retains no prompt/response data for this model. */
63
+ zeroDataRetention?: boolean;
64
+ /** The vendor will sign a BAA covering this model. */
65
+ hipaaEligible?: boolean;
66
+ [attribute: string]: unknown;
67
+ }
68
+ /**
69
+ * The policy a model-enforcement seam (`validateChatModelId`,
70
+ * `runWithModelFailover`, `createChatTurnRoutes`, `createSandboxChatProducer`)
71
+ * takes: which metadata to classify by, and which filter to apply. One shape,
72
+ * threaded unchanged through every seam so a product states its admission
73
+ * policy exactly once.
74
+ */
75
+ export interface ModelPolicy<C extends object = PolicyFilterContext> {
76
+ filter: PolicyFilter<ModelPolicyInfo, C>;
77
+ catalog: PolicyCatalog<ModelPolicyInfo>;
78
+ /** Threaded to the filter unchanged. */
79
+ context?: C;
80
+ }
81
+ /**
82
+ * Where enforcement reads an item's metadata: an in-memory iterable, a
83
+ * `ReadonlyMap` keyed by id, or a lookup (sync or async) keyed by the item's
84
+ * id. Returning `undefined` means UNCLASSIFIED — enforcement refuses the item.
85
+ *
86
+ * Key the catalog by the EXACT id string the turn carries (see
87
+ * docs/policy-filters.md § "Ids are exact strings"). Lookups never normalize.
88
+ */
89
+ export type PolicyCatalog<T extends {
90
+ id: string;
91
+ }> = Iterable<T> | ReadonlyMap<string, T> | ((id: string) => T | undefined | Promise<T | undefined>);
92
+ /** Why a policy check refused, machine-readable on the result and on {@link PolicyDeniedError}. */
93
+ export type PolicyDeniedCode = 'policy-metadata-missing' | 'policy-denied' | 'policy-filter-error';
94
+ /** The outcome of one policy check. Admission carries the item's metadata; a refusal carries a code and a reason. */
95
+ export type PolicyCheckResult<T = unknown> = {
96
+ allowed: true;
97
+ info: T;
98
+ } | {
99
+ allowed: false;
100
+ code: PolicyDeniedCode;
101
+ reason: string;
102
+ };
103
+ /** The error {@link assertPolicy} throws on any refusal. */
104
+ export declare class PolicyDeniedError extends Error {
105
+ readonly code: PolicyDeniedCode;
106
+ readonly kind: string;
107
+ readonly id: string;
108
+ constructor(code: PolicyDeniedCode, kind: string, id: string, reason: string);
109
+ }
110
+ /** Input to one policy check: what is being admitted, from which catalog, under which filter. */
111
+ export interface PolicyCheckInput<T extends {
112
+ id: string;
113
+ }, C extends object = PolicyFilterContext> {
114
+ /** What is being admitted ('model' | 'harness' | 'profile' | …) — names the refusal. */
115
+ kind: string;
116
+ /** The id being admitted. */
117
+ id: string;
118
+ /** The product's policy metadata. */
119
+ catalog: PolicyCatalog<T>;
120
+ /** The product's admission filter. */
121
+ filter: PolicyFilter<T, C>;
122
+ /** Threaded to the filter unchanged. */
123
+ context?: C;
124
+ }
125
+ /**
126
+ * Fail-closed policy check. Admits only when the catalog classifies the id AND
127
+ * the filter accepts it. Unclassified ids and filter errors refuse.
128
+ */
129
+ export declare function checkPolicy<T extends {
130
+ id: string;
131
+ }, C extends object = PolicyFilterContext>(input: PolicyCheckInput<T, C>): Promise<PolicyCheckResult<T>>;
132
+ /**
133
+ * The throwing form of {@link checkPolicy} for enforcement seams that fail by
134
+ * exception. Resolves with the item's metadata on admission; throws
135
+ * {@link PolicyDeniedError} on any refusal.
136
+ */
137
+ export declare function assertPolicy<T extends {
138
+ id: string;
139
+ }, C extends object = PolicyFilterContext>(input: PolicyCheckInput<T, C>): Promise<T>;
140
+ /** Policy check over a model id. See {@link checkPolicy}. */
141
+ export declare function checkModelPolicy<C extends object = PolicyFilterContext>(id: string, catalog: PolicyCatalog<ModelPolicyInfo>, filter: PolicyFilter<ModelPolicyInfo, C>, context?: C): Promise<PolicyCheckResult<ModelPolicyInfo>>;
142
+ /**
143
+ * Browser-safe catalog filter for pickers: keep only items the policy admits.
144
+ * Items the catalog does not classify are dropped — a picker offers exactly
145
+ * what a turn would run, and no more. Per docs/ui-picker-canon.md the picker
146
+ * itself still reconciles an absent SAVED value under its own name; this
147
+ * filter shapes the offered list, never the saved value.
148
+ *
149
+ * An iterable catalog is indexed ONCE into a Map up front: per-item rescans
150
+ * are O(items × catalog) and silently return nothing on a one-shot iterable.
151
+ */
152
+ export declare function filterItemsByPolicy<Item extends {
153
+ id: string;
154
+ }, Info extends {
155
+ id: string;
156
+ }, C extends object = PolicyFilterContext>(items: readonly Item[], catalog: PolicyCatalog<Info>, filter: PolicyFilter<Info, C>, context?: C): Promise<Item[]>;
157
+ /**
158
+ * Index an iterable catalog into a `Map` keyed by exact id — the form to hold
159
+ * when one catalog answers many lookups (a picker filter, a failover chain, a
160
+ * busy route). Ids must be unique: a Map keeps the LAST entry for a repeated
161
+ * id while a linear scan of the iterable finds the FIRST, and a catalog with
162
+ * duplicate ids is a product bug either way.
163
+ */
164
+ export declare function indexPolicyCatalog<T extends {
165
+ id: string;
166
+ }>(catalog: Iterable<T>): Map<string, T>;
@@ -0,0 +1,23 @@
1
+ import {
2
+ PolicyDeniedError,
3
+ allOf,
4
+ allowAll,
5
+ anyOf,
6
+ assertPolicy,
7
+ checkModelPolicy,
8
+ checkPolicy,
9
+ filterItemsByPolicy,
10
+ indexPolicyCatalog
11
+ } from "../chunk-XELWWXJJ.js";
12
+ export {
13
+ PolicyDeniedError,
14
+ allOf,
15
+ allowAll,
16
+ anyOf,
17
+ assertPolicy,
18
+ checkModelPolicy,
19
+ checkPolicy,
20
+ filterItemsByPolicy,
21
+ indexPolicyCatalog
22
+ };
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -121,9 +121,9 @@ export declare function createPublicConsultation(options: PublicConsultationOpti
121
121
  executionBudget?: ChatTurnExecutionLimits;
122
122
  maxOutputTokens?: number;
123
123
  }) => Promise<{
124
- start?: undefined;
125
124
  status: 'unsupported';
126
125
  reason: string;
126
+ start?: undefined;
127
127
  } | {
128
128
  reason?: undefined;
129
129
  status: 'prepared';
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createChatTurnRoutes,
3
3
  streamChatRouteAsSandboxEvents
4
- } from "../chunk-Q2H5FKVG.js";
4
+ } from "../chunk-XP77DJEF.js";
5
5
  import "../chunk-Q7WEEN7P.js";
6
6
  import "../chunk-UTAIRVOK.js";
7
7
  import "../chunk-RN4WPTBH.js";
@@ -16,6 +16,7 @@ import "../chunk-TXD5HXLE.js";
16
16
  import "../chunk-ZGUXI5SI.js";
17
17
  import "../chunk-6VQCFJWP.js";
18
18
  import "../chunk-LQYIBL3G.js";
19
+ import "../chunk-XELWWXJJ.js";
19
20
 
20
21
  // src/public-consultation/index.ts
21
22
  import { searchKnowledge } from "@tangle-network/agent-knowledge";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/public-consultation/index.ts"],"sourcesContent":["import { searchKnowledge, type KnowledgeIndex, type KnowledgePage } from '@tangle-network/agent-knowledge'\nimport { createChatTurnRoutes, type ChatTurnExecutionLimits, type ChatTurnLock, type ChatTurnMessageStore, type ChatTurnRouteProducer } from '../chat-routes/turn-routes'\nimport { streamChatRouteAsSandboxEvents } from '../chat-routes/gateway-adapter'\nimport type { TurnEventStore } from '../stream/turn-buffer'\nimport type { McpToolDefinition } from '../tools/mcp-rpc'\n\nexport interface PublishedKnowledgePage {\n readonly id: string\n readonly title: string\n readonly text: string\n}\n\n/** A server-created publication. Every selected page's complete text is public. */\nexport interface KnowledgePublication {\n readonly id: string\n readonly revision: string\n readonly ownerId: string\n readonly systemPrompt: string\n read(pageId: string): PublishedKnowledgePage | null\n search(query: string): PublishedKnowledgePage[]\n}\n\n/** Select content at publication time, before any payer or model input exists. */\nexport function createKnowledgePublication(input: {\n id: string\n revision: string\n ownerId: string\n systemPrompt: string\n index: KnowledgeIndex\n pageIds: readonly string[]\n}): KnowledgePublication {\n for (const value of [input.id, input.revision, input.ownerId]) requireIdentity(value)\n const pages: KnowledgePage[] = []\n for (const id of new Set(input.pageIds)) {\n const matches = input.index.pages.filter(page => page.id === id)\n if (matches.length !== 1) throw new Error('Publication page must resolve exactly once')\n const page = matches[0]!\n // Owner metadata, paths, links and source records can refer to private resources.\n pages.push({ id: page.id, title: page.title, text: page.text, path: page.id,\n frontmatter: {}, sourceIds: [], tags: [], outLinks: [] })\n }\n const index: KnowledgeIndex = { root: '', generatedAt: '', sources: [], pages, graph: { nodes: [], edges: [] } }\n const publicPage = (page: KnowledgePage): PublishedKnowledgePage =>\n Object.freeze({ id: page.id, title: page.title, text: page.text })\n return Object.freeze({\n id: input.id, revision: input.revision, ownerId: input.ownerId, systemPrompt: input.systemPrompt,\n read: (id: string) => {\n const page = pages.find(candidate => candidate.id === id)\n return page ? publicPage(page) : null\n },\n search: (query: string) => searchKnowledge(index, query, { limit: 10 }).map(hit => publicPage(hit.page)),\n })\n}\n\n/** Authenticated server identity. Never construct this from model arguments or request JSON. */\nexport interface ConsultationConsumer {\n method: string\n consumerId: string\n keyId?: string\n ownerId?: string\n requestId: string\n threadId?: string\n}\n\nexport interface ConsultationAgent {\n id: string\n ownerId: string\n}\n\nexport interface ConsultationIdentity {\n readonly publicationId: string\n readonly publicationRevision: string\n readonly consumerId: string\n readonly paymentMethod: string\n readonly workspaceId: string\n readonly threadId: string\n}\n\nexport interface ConsultationExecutionControl {\n readonly identity: ConsultationIdentity\n readonly signal: AbortSignal\n readonly executionLimits?: Readonly<ChatTurnExecutionLimits>\n}\n\nexport type PreparedConsultationExecution =\n | { status: 'unsupported'; reason: string }\n | { status: 'prepared'; produce(input: ConsultationExecution): ChatTurnRouteProducer | Promise<ChatTurnRouteProducer> }\n\nexport interface ConsultationExecution extends ConsultationExecutionControl {\n readonly identity: ConsultationIdentity\n readonly systemPrompt: string\n readonly prompt: string\n readonly priorMessages: ReadonlyArray<{ role: string; content: string }>\n readonly tools: McpToolDefinition[]\n}\n\nexport interface PublicConsultationOptions {\n /** Resolve only deliberately published snapshots. Return null after revocation. */\n resolvePublication(agentId: string): Promise<KnowledgePublication | null>\n /** Payment authentication does not substitute for this admission decision. */\n allowConsumer(publication: KnowledgePublication, consumer: ConsultationConsumer): Promise<boolean>\n store: ChatTurnMessageStore\n turnStore: TurnEventStore\n /** Shared durable single-flight lock. Use createDurableTurnLock with thread scope. */\n turnLock: ChatTurnLock<void>\n /** Create or read the stored parent row. Return its actual persisted ownership. */\n ensureConversation(identity: ConsultationIdentity): Promise<{ workspaceId: string; threadId: string }>\n /** Prepare without compute. Refuse limits the isolated executor cannot enforce across all model and tool calls. */\n prepareExecution(input: ConsultationExecutionControl): Promise<PreparedConsultationExecution>\n}\n\ninterface GatewayConsultationContext {\n consumerId: string\n paymentMethod: string\n requestId: string\n threadId?: string\n keyInfo: { keyId: string; ownerId?: string } | null\n messages: Array<{ role: string; content: string }>\n}\n\n/** Compose gateway admission, scoped knowledge and the maintained persisted chat route. */\nexport function createPublicConsultation(options: PublicConsultationOptions) {\n if (typeof options.turnLock?.acquire !== 'function' || typeof options.turnLock.release !== 'function') {\n throw new Error('Public consultation requires a shared turn lock')\n }\n async function ownsConversation(identity: ConsultationIdentity) {\n const conversation = await options.ensureConversation(identity)\n return conversation?.workspaceId === identity.workspaceId && conversation.threadId === identity.threadId\n }\n async function resolve(agent: ConsultationAgent, consumer: ConsultationConsumer) {\n requireIdentity(consumer.consumerId)\n requireIdentity(consumer.method)\n requireIdentity(consumer.threadId)\n const publication = await options.resolvePublication(agent.id)\n if (!publication || publication.id !== agent.id || publication.ownerId !== agent.ownerId\n || await options.allowConsumer(publication, consumer) !== true) return null\n const workspaceId = await scopedId([publication.ownerId, publication.id, publication.revision, consumer.method, consumer.consumerId])\n const threadId = await scopedId([workspaceId, consumer.threadId!])\n const identity: ConsultationIdentity = Object.freeze({\n publicationId: publication.id, publicationRevision: publication.revision,\n consumerId: consumer.consumerId, paymentMethod: consumer.method, workspaceId, threadId,\n })\n return { publication, identity }\n }\n\n return {\n async authorizeConsumer(agent: ConsultationAgent, consumer: ConsultationConsumer) {\n const scope = await resolve(agent, consumer)\n return scope\n ? { allow: true as const }\n : { allow: false as const, reason: 'Consultation access denied', code: 'consultation_forbidden' }\n },\n async readKnowledge(agent: ConsultationAgent, consumer: ConsultationConsumer, pageId: string) {\n const scope = await resolve(agent, consumer)\n if (!scope) throw new Error('Consultation access denied')\n return scope.publication.read(pageId)\n },\n async readHistory(agent: ConsultationAgent, consumer: ConsultationConsumer) {\n const scope = await resolve(agent, consumer)\n if (!scope) throw new Error('Consultation access denied')\n if (!await ownsConversation(scope.identity)) throw new Error('Consultation conversation conflict')\n return (await options.store.listMessages(scope.identity.threadId))\n .map(({ role, content }) => ({ role, content }))\n },\n async getSandbox(agent: ConsultationAgent, context?: GatewayConsultationContext) {\n if (!context) throw new Error('Authenticated consultation identity is required')\n const consumer: ConsultationConsumer = {\n consumerId: context.consumerId, method: context.paymentMethod,\n requestId: context.requestId, threadId: context.threadId,\n keyId: context.keyInfo?.keyId, ownerId: context.keyInfo?.ownerId,\n }\n const scope = await resolve(agent, consumer)\n if (!scope) throw new Error('Consultation access denied')\n // Client-supplied history and owner system prompts never enter this producer.\n const prompt = context.messages.filter(message => message.role === 'user').at(-1)?.content\n if (!prompt) throw new Error('Consultation requires a user message')\n type PromptOptions = { signal?: AbortSignal; executionBudget?: ChatTurnExecutionLimits; maxOutputTokens?: number }\n const prepare = async (streamOptions?: PromptOptions) => {\n const signal = streamOptions?.signal ?? new AbortController().signal\n signal.throwIfAborted()\n const supplied = streamOptions?.executionBudget\n const executionLimits = supplied || streamOptions?.maxOutputTokens !== undefined\n ? Object.freeze({ ...supplied, ...(streamOptions?.maxOutputTokens !== undefined\n ? { maxOutputTokens: Math.min(streamOptions.maxOutputTokens, supplied?.maxOutputTokens ?? Infinity) } : {}) })\n : undefined\n if (executionLimits && Object.values(executionLimits).some(value => typeof value !== 'number' || !Number.isFinite(value) || value < 0)) {\n return { status: 'unsupported' as const, reason: 'Invalid consultation execution limits' }\n }\n const control = Object.freeze({ identity: scope.identity, signal, executionLimits })\n const prepared = await options.prepareExecution(control)\n signal.throwIfAborted()\n if (prepared?.status !== 'prepared' || typeof prepared.produce !== 'function') {\n return { status: 'unsupported' as const, reason: prepared?.status === 'unsupported'\n ? prepared.reason : 'Invalid consultation execution preparation' }\n }\n let started = false\n const start = () => {\n signal.throwIfAborted()\n if (started) throw new Error('Consultation execution already started')\n started = true\n const routes = createChatTurnRoutes({\n projectId: 'public-consultation', store: options.store, turnStore: options.turnStore, turnLock: options.turnLock,\n authorize: async ({ intent, body }) => {\n const current = await resolve(agent, consumer)\n if (intent !== 'turn' || !current\n || current.identity.workspaceId !== scope.identity.workspaceId\n || body?.workspaceId !== scope.identity.workspaceId || body.threadId !== scope.identity.threadId) {\n return { ok: false, response: Response.json({ error: 'Consultation access denied' }, { status: 403 }) }\n }\n if (!await ownsConversation(scope.identity)) {\n return { ok: false, response: Response.json({ error: 'Consultation conversation conflict' }, { status: 403 }) }\n }\n return { ok: true, tenantId: scope.identity.workspaceId,\n userId: scope.identity.consumerId, context: undefined }\n },\n produce: args => {\n signal.throwIfAborted()\n return prepared.produce({\n ...control,\n identity: scope.identity, systemPrompt: scope.publication.systemPrompt, prompt,\n priorMessages: args.priorMessages.map(({ role, content }) => ({ role, content })),\n tools: publicationTools(scope.publication, async () => {\n signal.throwIfAborted()\n const current = await resolve(agent, consumer)\n signal.throwIfAborted()\n if (!current || current.identity.workspaceId !== scope.identity.workspaceId) {\n throw new Error('Consultation access denied')\n }\n }),\n })\n },\n })\n return streamChatRouteAsSandboxEvents({\n routes, request: new Request('https://consultation.invalid/internal'),\n payload: { workspaceId: scope.identity.workspaceId, threadId: scope.identity.threadId,\n content: prompt, turnId: consumer.requestId }, signal, executionLimits,\n })\n }\n return { status: 'prepared' as const, start }\n }\n return {\n prepareBudgetedPrompt: (_message: string, streamOptions: PromptOptions) => prepare(streamOptions),\n streamPrompt: async function* (_message: string, streamOptions?: PromptOptions) {\n const prepared = await prepare(streamOptions)\n if (prepared.status !== 'prepared') throw new Error(prepared.reason)\n yield* prepared.start()\n },\n }\n },\n }\n}\n\nfunction publicationTools(publication: KnowledgePublication, authorize: () => Promise<void>): McpToolDefinition[] {\n return [\n { name: 'knowledge_search', description: 'Search published consultation knowledge.',\n inputSchema: { type: 'object', required: ['query'], properties: { query: { type: 'string' } }, additionalProperties: false },\n run: async args => { await authorize(); return publication.search(singleArgument(args, 'query')) } },\n { name: 'knowledge_read', description: 'Read a published page by its exact citation id.',\n inputSchema: { type: 'object', required: ['pageId'], properties: { pageId: { type: 'string' } }, additionalProperties: false },\n run: async args => { await authorize(); return publication.read(singleArgument(args, 'pageId')) } },\n ]\n}\n\nfunction singleArgument(args: Record<string, unknown>, name: string): string {\n const value = args[name]\n if (Object.keys(args).length !== 1 || typeof value !== 'string' || !value.trim() || value.length > 8192) {\n throw new Error('Invalid published knowledge arguments')\n }\n return value\n}\n\nfunction requireIdentity(value: unknown): asserts value is string {\n if (typeof value !== 'string' || !value.trim() || value.length > 512) throw new Error('Invalid consultation identity')\n}\n\nasync function scopedId(parts: string[]): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(JSON.stringify(parts)))\n return `consultation:${Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join('')}`\n}\n"],"mappings":"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","names":["page"]}
1
+ {"version":3,"sources":["../../src/public-consultation/index.ts"],"sourcesContent":["import { searchKnowledge, type KnowledgeIndex, type KnowledgePage } from '@tangle-network/agent-knowledge'\nimport { createChatTurnRoutes, type ChatTurnExecutionLimits, type ChatTurnLock, type ChatTurnMessageStore, type ChatTurnRouteProducer } from '../chat-routes/turn-routes'\nimport { streamChatRouteAsSandboxEvents } from '../chat-routes/gateway-adapter'\nimport type { TurnEventStore } from '../stream/turn-buffer'\nimport type { McpToolDefinition } from '../tools/mcp-rpc'\n\nexport interface PublishedKnowledgePage {\n readonly id: string\n readonly title: string\n readonly text: string\n}\n\n/** A server-created publication. Every selected page's complete text is public. */\nexport interface KnowledgePublication {\n readonly id: string\n readonly revision: string\n readonly ownerId: string\n readonly systemPrompt: string\n read(pageId: string): PublishedKnowledgePage | null\n search(query: string): PublishedKnowledgePage[]\n}\n\n/** Select content at publication time, before any payer or model input exists. */\nexport function createKnowledgePublication(input: {\n id: string\n revision: string\n ownerId: string\n systemPrompt: string\n index: KnowledgeIndex\n pageIds: readonly string[]\n}): KnowledgePublication {\n for (const value of [input.id, input.revision, input.ownerId]) requireIdentity(value)\n const pages: KnowledgePage[] = []\n for (const id of new Set(input.pageIds)) {\n const matches = input.index.pages.filter(page => page.id === id)\n if (matches.length !== 1) throw new Error('Publication page must resolve exactly once')\n const page = matches[0]!\n // Owner metadata, paths, links and source records can refer to private resources.\n pages.push({ id: page.id, title: page.title, text: page.text, path: page.id,\n frontmatter: {}, sourceIds: [], tags: [], outLinks: [] })\n }\n const index: KnowledgeIndex = { root: '', generatedAt: '', sources: [], pages, graph: { nodes: [], edges: [] } }\n const publicPage = (page: KnowledgePage): PublishedKnowledgePage =>\n Object.freeze({ id: page.id, title: page.title, text: page.text })\n return Object.freeze({\n id: input.id, revision: input.revision, ownerId: input.ownerId, systemPrompt: input.systemPrompt,\n read: (id: string) => {\n const page = pages.find(candidate => candidate.id === id)\n return page ? publicPage(page) : null\n },\n search: (query: string) => searchKnowledge(index, query, { limit: 10 }).map(hit => publicPage(hit.page)),\n })\n}\n\n/** Authenticated server identity. Never construct this from model arguments or request JSON. */\nexport interface ConsultationConsumer {\n method: string\n consumerId: string\n keyId?: string\n ownerId?: string\n requestId: string\n threadId?: string\n}\n\nexport interface ConsultationAgent {\n id: string\n ownerId: string\n}\n\nexport interface ConsultationIdentity {\n readonly publicationId: string\n readonly publicationRevision: string\n readonly consumerId: string\n readonly paymentMethod: string\n readonly workspaceId: string\n readonly threadId: string\n}\n\nexport interface ConsultationExecutionControl {\n readonly identity: ConsultationIdentity\n readonly signal: AbortSignal\n readonly executionLimits?: Readonly<ChatTurnExecutionLimits>\n}\n\nexport type PreparedConsultationExecution =\n | { status: 'unsupported'; reason: string }\n | { status: 'prepared'; produce(input: ConsultationExecution): ChatTurnRouteProducer | Promise<ChatTurnRouteProducer> }\n\nexport interface ConsultationExecution extends ConsultationExecutionControl {\n readonly identity: ConsultationIdentity\n readonly systemPrompt: string\n readonly prompt: string\n readonly priorMessages: ReadonlyArray<{ role: string; content: string }>\n readonly tools: McpToolDefinition[]\n}\n\nexport interface PublicConsultationOptions {\n /** Resolve only deliberately published snapshots. Return null after revocation. */\n resolvePublication(agentId: string): Promise<KnowledgePublication | null>\n /** Payment authentication does not substitute for this admission decision. */\n allowConsumer(publication: KnowledgePublication, consumer: ConsultationConsumer): Promise<boolean>\n store: ChatTurnMessageStore\n turnStore: TurnEventStore\n /** Shared durable single-flight lock. Use createDurableTurnLock with thread scope. */\n turnLock: ChatTurnLock<void>\n /** Create or read the stored parent row. Return its actual persisted ownership. */\n ensureConversation(identity: ConsultationIdentity): Promise<{ workspaceId: string; threadId: string }>\n /** Prepare without compute. Refuse limits the isolated executor cannot enforce across all model and tool calls. */\n prepareExecution(input: ConsultationExecutionControl): Promise<PreparedConsultationExecution>\n}\n\ninterface GatewayConsultationContext {\n consumerId: string\n paymentMethod: string\n requestId: string\n threadId?: string\n keyInfo: { keyId: string; ownerId?: string } | null\n messages: Array<{ role: string; content: string }>\n}\n\n/** Compose gateway admission, scoped knowledge and the maintained persisted chat route. */\nexport function createPublicConsultation(options: PublicConsultationOptions) {\n if (typeof options.turnLock?.acquire !== 'function' || typeof options.turnLock.release !== 'function') {\n throw new Error('Public consultation requires a shared turn lock')\n }\n async function ownsConversation(identity: ConsultationIdentity) {\n const conversation = await options.ensureConversation(identity)\n return conversation?.workspaceId === identity.workspaceId && conversation.threadId === identity.threadId\n }\n async function resolve(agent: ConsultationAgent, consumer: ConsultationConsumer) {\n requireIdentity(consumer.consumerId)\n requireIdentity(consumer.method)\n requireIdentity(consumer.threadId)\n const publication = await options.resolvePublication(agent.id)\n if (!publication || publication.id !== agent.id || publication.ownerId !== agent.ownerId\n || await options.allowConsumer(publication, consumer) !== true) return null\n const workspaceId = await scopedId([publication.ownerId, publication.id, publication.revision, consumer.method, consumer.consumerId])\n const threadId = await scopedId([workspaceId, consumer.threadId!])\n const identity: ConsultationIdentity = Object.freeze({\n publicationId: publication.id, publicationRevision: publication.revision,\n consumerId: consumer.consumerId, paymentMethod: consumer.method, workspaceId, threadId,\n })\n return { publication, identity }\n }\n\n return {\n async authorizeConsumer(agent: ConsultationAgent, consumer: ConsultationConsumer) {\n const scope = await resolve(agent, consumer)\n return scope\n ? { allow: true as const }\n : { allow: false as const, reason: 'Consultation access denied', code: 'consultation_forbidden' }\n },\n async readKnowledge(agent: ConsultationAgent, consumer: ConsultationConsumer, pageId: string) {\n const scope = await resolve(agent, consumer)\n if (!scope) throw new Error('Consultation access denied')\n return scope.publication.read(pageId)\n },\n async readHistory(agent: ConsultationAgent, consumer: ConsultationConsumer) {\n const scope = await resolve(agent, consumer)\n if (!scope) throw new Error('Consultation access denied')\n if (!await ownsConversation(scope.identity)) throw new Error('Consultation conversation conflict')\n return (await options.store.listMessages(scope.identity.threadId))\n .map(({ role, content }) => ({ role, content }))\n },\n async getSandbox(agent: ConsultationAgent, context?: GatewayConsultationContext) {\n if (!context) throw new Error('Authenticated consultation identity is required')\n const consumer: ConsultationConsumer = {\n consumerId: context.consumerId, method: context.paymentMethod,\n requestId: context.requestId, threadId: context.threadId,\n keyId: context.keyInfo?.keyId, ownerId: context.keyInfo?.ownerId,\n }\n const scope = await resolve(agent, consumer)\n if (!scope) throw new Error('Consultation access denied')\n // Client-supplied history and owner system prompts never enter this producer.\n const prompt = context.messages.filter(message => message.role === 'user').at(-1)?.content\n if (!prompt) throw new Error('Consultation requires a user message')\n type PromptOptions = { signal?: AbortSignal; executionBudget?: ChatTurnExecutionLimits; maxOutputTokens?: number }\n const prepare = async (streamOptions?: PromptOptions) => {\n const signal = streamOptions?.signal ?? new AbortController().signal\n signal.throwIfAborted()\n const supplied = streamOptions?.executionBudget\n const executionLimits = supplied || streamOptions?.maxOutputTokens !== undefined\n ? Object.freeze({ ...supplied, ...(streamOptions?.maxOutputTokens !== undefined\n ? { maxOutputTokens: Math.min(streamOptions.maxOutputTokens, supplied?.maxOutputTokens ?? Infinity) } : {}) })\n : undefined\n if (executionLimits && Object.values(executionLimits).some(value => typeof value !== 'number' || !Number.isFinite(value) || value < 0)) {\n return { status: 'unsupported' as const, reason: 'Invalid consultation execution limits' }\n }\n const control = Object.freeze({ identity: scope.identity, signal, executionLimits })\n const prepared = await options.prepareExecution(control)\n signal.throwIfAborted()\n if (prepared?.status !== 'prepared' || typeof prepared.produce !== 'function') {\n return { status: 'unsupported' as const, reason: prepared?.status === 'unsupported'\n ? prepared.reason : 'Invalid consultation execution preparation' }\n }\n let started = false\n const start = () => {\n signal.throwIfAborted()\n if (started) throw new Error('Consultation execution already started')\n started = true\n const routes = createChatTurnRoutes({\n projectId: 'public-consultation', store: options.store, turnStore: options.turnStore, turnLock: options.turnLock,\n authorize: async ({ intent, body }) => {\n const current = await resolve(agent, consumer)\n if (intent !== 'turn' || !current\n || current.identity.workspaceId !== scope.identity.workspaceId\n || body?.workspaceId !== scope.identity.workspaceId || body.threadId !== scope.identity.threadId) {\n return { ok: false, response: Response.json({ error: 'Consultation access denied' }, { status: 403 }) }\n }\n if (!await ownsConversation(scope.identity)) {\n return { ok: false, response: Response.json({ error: 'Consultation conversation conflict' }, { status: 403 }) }\n }\n return { ok: true, tenantId: scope.identity.workspaceId,\n userId: scope.identity.consumerId, context: undefined }\n },\n produce: args => {\n signal.throwIfAborted()\n return prepared.produce({\n ...control,\n identity: scope.identity, systemPrompt: scope.publication.systemPrompt, prompt,\n priorMessages: args.priorMessages.map(({ role, content }) => ({ role, content })),\n tools: publicationTools(scope.publication, async () => {\n signal.throwIfAborted()\n const current = await resolve(agent, consumer)\n signal.throwIfAborted()\n if (!current || current.identity.workspaceId !== scope.identity.workspaceId) {\n throw new Error('Consultation access denied')\n }\n }),\n })\n },\n })\n return streamChatRouteAsSandboxEvents({\n routes, request: new Request('https://consultation.invalid/internal'),\n payload: { workspaceId: scope.identity.workspaceId, threadId: scope.identity.threadId,\n content: prompt, turnId: consumer.requestId }, signal, executionLimits,\n })\n }\n return { status: 'prepared' as const, start }\n }\n return {\n prepareBudgetedPrompt: (_message: string, streamOptions: PromptOptions) => prepare(streamOptions),\n streamPrompt: async function* (_message: string, streamOptions?: PromptOptions) {\n const prepared = await prepare(streamOptions)\n if (prepared.status !== 'prepared') throw new Error(prepared.reason)\n yield* prepared.start()\n },\n }\n },\n }\n}\n\nfunction publicationTools(publication: KnowledgePublication, authorize: () => Promise<void>): McpToolDefinition[] {\n return [\n { name: 'knowledge_search', description: 'Search published consultation knowledge.',\n inputSchema: { type: 'object', required: ['query'], properties: { query: { type: 'string' } }, additionalProperties: false },\n run: async args => { await authorize(); return publication.search(singleArgument(args, 'query')) } },\n { name: 'knowledge_read', description: 'Read a published page by its exact citation id.',\n inputSchema: { type: 'object', required: ['pageId'], properties: { pageId: { type: 'string' } }, additionalProperties: false },\n run: async args => { await authorize(); return publication.read(singleArgument(args, 'pageId')) } },\n ]\n}\n\nfunction singleArgument(args: Record<string, unknown>, name: string): string {\n const value = args[name]\n if (Object.keys(args).length !== 1 || typeof value !== 'string' || !value.trim() || value.length > 8192) {\n throw new Error('Invalid published knowledge arguments')\n }\n return value\n}\n\nfunction requireIdentity(value: unknown): asserts value is string {\n if (typeof value !== 'string' || !value.trim() || value.length > 512) throw new Error('Invalid consultation identity')\n}\n\nasync function scopedId(parts: string[]): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(JSON.stringify(parts)))\n return `consultation:${Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join('')}`\n}\n"],"mappings":"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","names":["page"]}
@@ -138,10 +138,10 @@ import {
138
138
  } from "../chunk-4LBCTFBP.js";
139
139
  import "../chunk-VBRUG4N5.js";
140
140
  import "../chunk-LWSJK546.js";
141
- import "../chunk-QAF7ZTO3.js";
141
+ import "../chunk-L5JLOC2F.js";
142
142
  import "../chunk-6A7MYOUI.js";
143
- import "../chunk-S5SRJJQG.js";
144
143
  import "../chunk-2RCPPOQ4.js";
144
+ import "../chunk-S5SRJJQG.js";
145
145
  import "../chunk-IJAXOQJU.js";
146
146
  import "../chunk-UGWQLQDS.js";
147
147
  import "../chunk-E65YV7VV.js";
@@ -21,12 +21,12 @@ import {
21
21
  useBrowserLayoutEffect,
22
22
  usePopover
23
23
  } from "../chunk-XG6FODYO.js";
24
- import "../chunk-RASGKEAL.js";
25
24
  import {
26
25
  confirmWrite,
27
26
  rejectWrite,
28
27
  useConfirmedMutation
29
28
  } from "../chunk-AVP5SNIG.js";
29
+ import "../chunk-RASGKEAL.js";
30
30
  import {
31
31
  useWorkspaceExport
32
32
  } from "../chunk-EZFBGW23.js";
@@ -9,7 +9,7 @@ import {
9
9
  restrictTaxonomy,
10
10
  verifyCapabilityToken,
11
11
  verifyExpiringCapabilityToken
12
- } from "../chunk-QAF7ZTO3.js";
12
+ } from "../chunk-L5JLOC2F.js";
13
13
  import {
14
14
  DEFAULT_APP_TOOL_PATHS,
15
15
  DEFAULT_HEADER_NAMES,
@@ -22,8 +22,8 @@ import {
22
22
  readToolArgs,
23
23
  unresolvableSurfaceCredential
24
24
  } from "../chunk-6A7MYOUI.js";
25
- import "../chunk-S5SRJJQG.js";
26
25
  import "../chunk-2RCPPOQ4.js";
26
+ import "../chunk-S5SRJJQG.js";
27
27
  import {
28
28
  APP_TOOL_NAMES,
29
29
  buildAppToolOpenAITools,
@@ -144,9 +144,6 @@ import {
144
144
  withoutRecordGridRemoved,
145
145
  withoutRecordGridUpdate
146
146
  } from "../chunk-KNTRS7OZ.js";
147
- import {
148
- MENTION_PILL_CLASS
149
- } from "../chunk-3HUFJ5LO.js";
150
147
  import {
151
148
  SessionHistoryPanel,
152
149
  formatSessionTimestamp,
@@ -154,6 +151,9 @@ import {
154
151
  useSessionActions,
155
152
  useSessionHistory
156
153
  } from "../chunk-KMIDRUUH.js";
154
+ import {
155
+ MENTION_PILL_CLASS
156
+ } from "../chunk-3HUFJ5LO.js";
157
157
  import "../chunk-FBVLEGEG.js";
158
158
  import {
159
159
  EvidenceLineageTable,
@@ -168,6 +168,9 @@ import {
168
168
  workProductStatusLabel,
169
169
  workProductStatusTone
170
170
  } from "../chunk-Y6PRDUFJ.js";
171
+ import {
172
+ parseReviewQueueItem
173
+ } from "../chunk-GEYACSFW.js";
171
174
  import {
172
175
  ChatComposer,
173
176
  ComposerModeControls,
@@ -226,17 +229,14 @@ import {
226
229
  usePending,
227
230
  usePopover
228
231
  } from "../chunk-XG6FODYO.js";
229
- import {
230
- parseReviewQueueItem
231
- } from "../chunk-GEYACSFW.js";
232
232
  import {
233
233
  ReviewFeedbackForm
234
234
  } from "../chunk-ATQQCG4I.js";
235
235
  import "../chunk-WSEXFTN4.js";
236
+ import "../chunk-AVP5SNIG.js";
236
237
  import {
237
238
  ProviderLogo
238
239
  } from "../chunk-RASGKEAL.js";
239
- import "../chunk-AVP5SNIG.js";
240
240
  import "../chunk-FQYAJYPW.js";
241
241
  import {
242
242
  ATTACHMENT_ACCEPT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.60.192",
3
+ "version": "0.60.193",
4
4
  "packageManager": "pnpm@11.24.0",
5
5
  "description": "Build agent applications with typed chat, tools, sandboxes, integrations, billing, and evaluation.",
6
6
  "keywords": [
@@ -194,6 +194,11 @@
194
194
  "import": "./dist/model-resolution/index.js",
195
195
  "default": "./dist/model-resolution/index.js"
196
196
  },
197
+ "./policy-filters": {
198
+ "types": "./dist/policy-filters/index.d.ts",
199
+ "import": "./dist/policy-filters/index.js",
200
+ "default": "./dist/policy-filters/index.js"
201
+ },
197
202
  "./sandbox": {
198
203
  "types": "./dist/sandbox/index.d.ts",
199
204
  "import": "./dist/sandbox/index.js",