@tangle-network/agent-app 0.60.192 → 0.60.194
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-auth/index.js +3 -3
- package/dist/assistant/index.js +3 -3
- package/dist/chat-routes/index.js +8 -5
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-routes/model-failover-stream.d.ts +8 -0
- package/dist/chat-routes/sandbox-producer.d.ts +12 -0
- package/dist/chat-routes/turn-routes.d.ts +14 -0
- package/dist/{chunk-QAF7ZTO3.js → chunk-L5JLOC2F.js} +4 -4
- package/dist/{chunk-QW4UOEPH.js → chunk-SUIH5B55.js} +5 -3
- package/dist/chunk-SUIH5B55.js.map +1 -0
- package/dist/chunk-XELWWXJJ.js +101 -0
- package/dist/chunk-XELWWXJJ.js.map +1 -0
- package/dist/{chunk-Q2H5FKVG.js → chunk-XP77DJEF.js} +16 -1
- package/dist/chunk-XP77DJEF.js.map +1 -0
- package/dist/{chunk-63EKFZOE.js → chunk-ZLX4BA27.js} +15 -1
- package/dist/chunk-ZLX4BA27.js.map +1 -0
- package/dist/model-resolution/failover.d.ts +16 -2
- package/dist/model-resolution/index.d.ts +42 -0
- package/dist/model-resolution/index.js +30 -5
- package/dist/model-resolution/index.js.map +1 -1
- package/dist/policy-filters/index.d.ts +166 -0
- package/dist/policy-filters/index.js +23 -0
- package/dist/policy-filters/index.js.map +1 -0
- package/dist/public-consultation/index.d.ts +1 -1
- package/dist/public-consultation/index.js +2 -1
- package/dist/public-consultation/index.js.map +1 -1
- package/dist/sandbox/index.d.ts +1 -0
- package/dist/sandbox/index.js +3 -3
- package/dist/sandbox/model.d.ts +4 -0
- package/dist/settings-react/index.js +1 -1
- package/dist/tools/index.js +2 -2
- package/dist/web-react/index.js +7 -7
- package/package.json +6 -1
- package/dist/chunk-63EKFZOE.js.map +0 -1
- package/dist/chunk-Q2H5FKVG.js.map +0 -1
- package/dist/chunk-QW4UOEPH.js.map +0 -1
- /package/dist/{chunk-QAF7ZTO3.js.map → chunk-L5JLOC2F.js.map} +0 -0
|
@@ -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":";;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,uBAAgE;AAuBlE,SAAS,2BAA2B,OAOlB;AACvB,aAAW,SAAS,CAAC,MAAM,IAAI,MAAM,UAAU,MAAM,OAAO,EAAG,iBAAgB,KAAK;AACpF,QAAM,QAAyB,CAAC;AAChC,aAAW,MAAM,IAAI,IAAI,MAAM,OAAO,GAAG;AACvC,UAAM,UAAU,MAAM,MAAM,MAAM,OAAO,CAAAA,UAAQA,MAAK,OAAO,EAAE;AAC/D,QAAI,QAAQ,WAAW,EAAG,OAAM,IAAI,MAAM,4CAA4C;AACtF,UAAM,OAAO,QAAQ,CAAC;AAEtB,UAAM,KAAK;AAAA,MAAE,IAAI,KAAK;AAAA,MAAI,OAAO,KAAK;AAAA,MAAO,MAAM,KAAK;AAAA,MAAM,MAAM,KAAK;AAAA,MACvE,aAAa,CAAC;AAAA,MAAG,WAAW,CAAC;AAAA,MAAG,MAAM,CAAC;AAAA,MAAG,UAAU,CAAC;AAAA,IAAE,CAAC;AAAA,EAC5D;AACA,QAAM,QAAwB,EAAE,MAAM,IAAI,aAAa,IAAI,SAAS,CAAC,GAAG,OAAO,OAAO,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,EAAE,EAAE;AAC/G,QAAM,aAAa,CAAC,SAClB,OAAO,OAAO,EAAE,IAAI,KAAK,IAAI,OAAO,KAAK,OAAO,MAAM,KAAK,KAAK,CAAC;AACnE,SAAO,OAAO,OAAO;AAAA,IACnB,IAAI,MAAM;AAAA,IAAI,UAAU,MAAM;AAAA,IAAU,SAAS,MAAM;AAAA,IAAS,cAAc,MAAM;AAAA,IACpF,MAAM,CAAC,OAAe;AACpB,YAAM,OAAO,MAAM,KAAK,eAAa,UAAU,OAAO,EAAE;AACxD,aAAO,OAAO,WAAW,IAAI,IAAI;AAAA,IACnC;AAAA,IACA,QAAQ,CAAC,UAAkB,gBAAgB,OAAO,OAAO,EAAE,OAAO,GAAG,CAAC,EAAE,IAAI,SAAO,WAAW,IAAI,IAAI,CAAC;AAAA,EACzG,CAAC;AACH;AAqEO,SAAS,yBAAyB,SAAoC;AAC3E,MAAI,OAAO,QAAQ,UAAU,YAAY,cAAc,OAAO,QAAQ,SAAS,YAAY,YAAY;AACrG,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AACA,iBAAe,iBAAiB,UAAgC;AAC9D,UAAM,eAAe,MAAM,QAAQ,mBAAmB,QAAQ;AAC9D,WAAO,cAAc,gBAAgB,SAAS,eAAe,aAAa,aAAa,SAAS;AAAA,EAClG;AACA,iBAAe,QAAQ,OAA0B,UAAgC;AAC/E,oBAAgB,SAAS,UAAU;AACnC,oBAAgB,SAAS,MAAM;AAC/B,oBAAgB,SAAS,QAAQ;AACjC,UAAM,cAAc,MAAM,QAAQ,mBAAmB,MAAM,EAAE;AAC7D,QAAI,CAAC,eAAe,YAAY,OAAO,MAAM,MAAM,YAAY,YAAY,MAAM,WAC5E,MAAM,QAAQ,cAAc,aAAa,QAAQ,MAAM,KAAM,QAAO;AACzE,UAAM,cAAc,MAAM,SAAS,CAAC,YAAY,SAAS,YAAY,IAAI,YAAY,UAAU,SAAS,QAAQ,SAAS,UAAU,CAAC;AACpI,UAAM,WAAW,MAAM,SAAS,CAAC,aAAa,SAAS,QAAS,CAAC;AACjE,UAAM,WAAiC,OAAO,OAAO;AAAA,MACnD,eAAe,YAAY;AAAA,MAAI,qBAAqB,YAAY;AAAA,MAChE,YAAY,SAAS;AAAA,MAAY,eAAe,SAAS;AAAA,MAAQ;AAAA,MAAa;AAAA,IAChF,CAAC;AACD,WAAO,EAAE,aAAa,SAAS;AAAA,EACjC;AAEA,SAAO;AAAA,IACL,MAAM,kBAAkB,OAA0B,UAAgC;AAChF,YAAM,QAAQ,MAAM,QAAQ,OAAO,QAAQ;AAC3C,aAAO,QACH,EAAE,OAAO,KAAc,IACvB,EAAE,OAAO,OAAgB,QAAQ,8BAA8B,MAAM,yBAAyB;AAAA,IACpG;AAAA,IACA,MAAM,cAAc,OAA0B,UAAgC,QAAgB;AAC5F,YAAM,QAAQ,MAAM,QAAQ,OAAO,QAAQ;AAC3C,UAAI,CAAC,MAAO,OAAM,IAAI,MAAM,4BAA4B;AACxD,aAAO,MAAM,YAAY,KAAK,MAAM;AAAA,IACtC;AAAA,IACA,MAAM,YAAY,OAA0B,UAAgC;AAC1E,YAAM,QAAQ,MAAM,QAAQ,OAAO,QAAQ;AAC3C,UAAI,CAAC,MAAO,OAAM,IAAI,MAAM,4BAA4B;AACxD,UAAI,CAAC,MAAM,iBAAiB,MAAM,QAAQ,EAAG,OAAM,IAAI,MAAM,oCAAoC;AACjG,cAAQ,MAAM,QAAQ,MAAM,aAAa,MAAM,SAAS,QAAQ,GAC7D,IAAI,CAAC,EAAE,MAAM,QAAQ,OAAO,EAAE,MAAM,QAAQ,EAAE;AAAA,IACnD;AAAA,IACA,MAAM,WAAW,OAA0B,SAAsC;AAC/E,UAAI,CAAC,QAAS,OAAM,IAAI,MAAM,iDAAiD;AAC/E,YAAM,WAAiC;AAAA,QACrC,YAAY,QAAQ;AAAA,QAAY,QAAQ,QAAQ;AAAA,QAChD,WAAW,QAAQ;AAAA,QAAW,UAAU,QAAQ;AAAA,QAChD,OAAO,QAAQ,SAAS;AAAA,QAAO,SAAS,QAAQ,SAAS;AAAA,MAC3D;AACA,YAAM,QAAQ,MAAM,QAAQ,OAAO,QAAQ;AAC3C,UAAI,CAAC,MAAO,OAAM,IAAI,MAAM,4BAA4B;AAExD,YAAM,SAAS,QAAQ,SAAS,OAAO,aAAW,QAAQ,SAAS,MAAM,EAAE,GAAG,EAAE,GAAG;AACnF,UAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,sCAAsC;AAEnE,YAAM,UAAU,OAAO,kBAAkC;AACvD,cAAM,SAAS,eAAe,UAAU,IAAI,gBAAgB,EAAE;AAC9D,eAAO,eAAe;AACtB,cAAM,WAAW,eAAe;AAChC,cAAM,kBAAkB,YAAY,eAAe,oBAAoB,SACnE,OAAO,OAAO,EAAE,GAAG,UAAU,GAAI,eAAe,oBAAoB,SAClE,EAAE,iBAAiB,KAAK,IAAI,cAAc,iBAAiB,UAAU,mBAAmB,QAAQ,EAAE,IAAI,CAAC,EAAG,CAAC,IAC7G;AACJ,YAAI,mBAAmB,OAAO,OAAO,eAAe,EAAE,KAAK,WAAS,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,CAAC,GAAG;AACtI,iBAAO,EAAE,QAAQ,eAAwB,QAAQ,wCAAwC;AAAA,QAC3F;AACA,cAAM,UAAU,OAAO,OAAO,EAAE,UAAU,MAAM,UAAU,QAAQ,gBAAgB,CAAC;AACnF,cAAM,WAAW,MAAM,QAAQ,iBAAiB,OAAO;AACvD,eAAO,eAAe;AACtB,YAAI,UAAU,WAAW,cAAc,OAAO,SAAS,YAAY,YAAY;AAC7E,iBAAO,EAAE,QAAQ,eAAwB,QAAQ,UAAU,WAAW,gBAClE,SAAS,SAAS,6CAA6C;AAAA,QACrE;AACA,YAAI,UAAU;AACd,cAAM,QAAQ,MAAM;AAClB,iBAAO,eAAe;AACtB,cAAI,QAAS,OAAM,IAAI,MAAM,wCAAwC;AACrE,oBAAU;AACV,gBAAM,SAAS,qBAAqB;AAAA,YAClC,WAAW;AAAA,YAAuB,OAAO,QAAQ;AAAA,YAAO,WAAW,QAAQ;AAAA,YAAW,UAAU,QAAQ;AAAA,YACxG,WAAW,OAAO,EAAE,QAAQ,KAAK,MAAM;AACrC,oBAAM,UAAU,MAAM,QAAQ,OAAO,QAAQ;AAC7C,kBAAI,WAAW,UAAU,CAAC,WACrB,QAAQ,SAAS,gBAAgB,MAAM,SAAS,eAChD,MAAM,gBAAgB,MAAM,SAAS,eAAe,KAAK,aAAa,MAAM,SAAS,UAAU;AAClG,uBAAO,EAAE,IAAI,OAAO,UAAU,SAAS,KAAK,EAAE,OAAO,6BAA6B,GAAG,EAAE,QAAQ,IAAI,CAAC,EAAE;AAAA,cACxG;AACA,kBAAI,CAAC,MAAM,iBAAiB,MAAM,QAAQ,GAAG;AAC3C,uBAAO,EAAE,IAAI,OAAO,UAAU,SAAS,KAAK,EAAE,OAAO,qCAAqC,GAAG,EAAE,QAAQ,IAAI,CAAC,EAAE;AAAA,cAChH;AACA,qBAAO;AAAA,gBAAE,IAAI;AAAA,gBAAM,UAAU,MAAM,SAAS;AAAA,gBAC1C,QAAQ,MAAM,SAAS;AAAA,gBAAY,SAAS;AAAA,cAAU;AAAA,YAC1D;AAAA,YACA,SAAS,UAAQ;AACf,qBAAO,eAAe;AACtB,qBAAO,SAAS,QAAQ;AAAA,gBACtB,GAAG;AAAA,gBACH,UAAU,MAAM;AAAA,gBAAU,cAAc,MAAM,YAAY;AAAA,gBAAc;AAAA,gBACxE,eAAe,KAAK,cAAc,IAAI,CAAC,EAAE,MAAM,QAAQ,OAAO,EAAE,MAAM,QAAQ,EAAE;AAAA,gBAChF,OAAO,iBAAiB,MAAM,aAAa,YAAY;AACrD,yBAAO,eAAe;AACtB,wBAAM,UAAU,MAAM,QAAQ,OAAO,QAAQ;AAC7C,yBAAO,eAAe;AACtB,sBAAI,CAAC,WAAW,QAAQ,SAAS,gBAAgB,MAAM,SAAS,aAAa;AAC3E,0BAAM,IAAI,MAAM,4BAA4B;AAAA,kBAC9C;AAAA,gBACF,CAAC;AAAA,cACH,CAAC;AAAA,YACH;AAAA,UACF,CAAC;AACD,iBAAO,+BAA+B;AAAA,YACpC;AAAA,YAAQ,SAAS,IAAI,QAAQ,uCAAuC;AAAA,YACpE,SAAS;AAAA,cAAE,aAAa,MAAM,SAAS;AAAA,cAAa,UAAU,MAAM,SAAS;AAAA,cAC3E,SAAS;AAAA,cAAQ,QAAQ,SAAS;AAAA,YAAU;AAAA,YAAG;AAAA,YAAQ;AAAA,UAC3D,CAAC;AAAA,QACH;AACA,eAAO,EAAE,QAAQ,YAAqB,MAAM;AAAA,MAC9C;AACA,aAAO;AAAA,QACL,uBAAuB,CAAC,UAAkB,kBAAiC,QAAQ,aAAa;AAAA,QAChG,cAAc,iBAAiB,UAAkB,eAA+B;AAC9E,gBAAM,WAAW,MAAM,QAAQ,aAAa;AAC5C,cAAI,SAAS,WAAW,WAAY,OAAM,IAAI,MAAM,SAAS,MAAM;AACnE,iBAAO,SAAS,MAAM;AAAA,QACxB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,iBAAiB,aAAmC,WAAqD;AAChH,SAAO;AAAA,IACL;AAAA,MAAE,MAAM;AAAA,MAAoB,aAAa;AAAA,MACvC,aAAa,EAAE,MAAM,UAAU,UAAU,CAAC,OAAO,GAAG,YAAY,EAAE,OAAO,EAAE,MAAM,SAAS,EAAE,GAAG,sBAAsB,MAAM;AAAA,MAC3H,KAAK,OAAM,SAAQ;AAAE,cAAM,UAAU;AAAG,eAAO,YAAY,OAAO,eAAe,MAAM,OAAO,CAAC;AAAA,MAAE;AAAA,IAAE;AAAA,IACrG;AAAA,MAAE,MAAM;AAAA,MAAkB,aAAa;AAAA,MACrC,aAAa,EAAE,MAAM,UAAU,UAAU,CAAC,QAAQ,GAAG,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,EAAE,GAAG,sBAAsB,MAAM;AAAA,MAC7H,KAAK,OAAM,SAAQ;AAAE,cAAM,UAAU;AAAG,eAAO,YAAY,KAAK,eAAe,MAAM,QAAQ,CAAC;AAAA,MAAE;AAAA,IAAE;AAAA,EACtG;AACF;AAEA,SAAS,eAAe,MAA+B,MAAsB;AAC3E,QAAM,QAAQ,KAAK,IAAI;AACvB,MAAI,OAAO,KAAK,IAAI,EAAE,WAAW,KAAK,OAAO,UAAU,YAAY,CAAC,MAAM,KAAK,KAAK,MAAM,SAAS,MAAM;AACvG,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACzD;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,OAAyC;AAChE,MAAI,OAAO,UAAU,YAAY,CAAC,MAAM,KAAK,KAAK,MAAM,SAAS,IAAK,OAAM,IAAI,MAAM,+BAA+B;AACvH;AAEA,eAAe,SAAS,OAAkC;AACxD,QAAM,SAAS,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI,YAAY,EAAE,OAAO,KAAK,UAAU,KAAK,CAAC,CAAC;AACpG,SAAO,gBAAgB,MAAM,KAAK,IAAI,WAAW,MAAM,GAAG,UAAQ,KAAK,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC;AAChH;","names":["page"]}
|
package/dist/sandbox/index.d.ts
CHANGED
package/dist/sandbox/index.js
CHANGED
|
@@ -97,7 +97,7 @@ import {
|
|
|
97
97
|
warmBinOf,
|
|
98
98
|
warmWeekdayOf,
|
|
99
99
|
writeProfileFilesToBox
|
|
100
|
-
} from "../chunk-
|
|
100
|
+
} from "../chunk-SUIH5B55.js";
|
|
101
101
|
import {
|
|
102
102
|
EGRESS_PROXY_RECOVERY_PHASE,
|
|
103
103
|
EGRESS_PROXY_RECOVERY_REQUIRED,
|
|
@@ -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-
|
|
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";
|
package/dist/sandbox/model.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface ProviderResolutionConfig {
|
|
|
7
7
|
defaultModel?: string;
|
|
8
8
|
openaiApiKey?: string;
|
|
9
9
|
allowKeylessModel?: boolean;
|
|
10
|
+
apiKeyEnv?: string;
|
|
10
11
|
}
|
|
11
12
|
/** Represent a fully configured model with optional API key and base URL for sandbox platform integration */
|
|
12
13
|
/**
|
|
@@ -26,6 +27,7 @@ export declare function backendModelFields(model: ResolvedModel): {
|
|
|
26
27
|
model: string;
|
|
27
28
|
provider: string;
|
|
28
29
|
apiKey?: string;
|
|
30
|
+
apiKeyEnv?: string;
|
|
29
31
|
baseUrl?: string;
|
|
30
32
|
};
|
|
31
33
|
};
|
|
@@ -33,6 +35,8 @@ export interface ResolvedModel {
|
|
|
33
35
|
model: string;
|
|
34
36
|
provider: string;
|
|
35
37
|
apiKey?: string;
|
|
38
|
+
/** A stored-secret reference standing in for `apiKey`; see {@link ProviderResolutionConfig.apiKeyEnv}. */
|
|
39
|
+
apiKeyEnv?: string;
|
|
36
40
|
baseUrl?: string;
|
|
37
41
|
}
|
|
38
42
|
/**
|
|
@@ -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";
|
package/dist/tools/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
restrictTaxonomy,
|
|
10
10
|
verifyCapabilityToken,
|
|
11
11
|
verifyExpiringCapabilityToken
|
|
12
|
-
} from "../chunk-
|
|
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,
|
package/dist/web-react/index.js
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.60.194",
|
|
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",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/model-resolution/failover.ts"],"sourcesContent":["/**\n * Model failover — the answer to \"one upstream hit a quota wall and the customer\n * got a Bad Gateway even though the router had abundant healthy capacity\".\n *\n * This is deliberately NOT a health probe. Probing before every turn buys a\n * round-trip of latency on the happy path and still races the outage (a model\n * healthy at probe time can 502 a second later). Failover is REACTIVE: run the\n * preferred model, and on an upstream-outage signal move to the next model in\n * the chain. Zero added latency when the preferred model works.\n *\n * Two facts drive the design, both measured against a live box during the\n * 2026-07-25 Anthropic/DeepSeek outage:\n *\n * 1. An outage is NOT always a thrown error. The sandbox resolves with a\n * payload — `{ success: false, errorCode: 'provider_inference_unavailable' }`\n * — so a classifier that only inspects `catch` misses the customer-visible\n * case entirely. `isUpstreamUnavailable` inspects resolved values too.\n * 2. Catalog membership is NOT liveness. The router's `/v1/models` still listed\n * every dead model during the outage, so `validateChatModelId` admitted\n * `claude-sonnet-4-6` while every call to it returned 502. Nothing upstream\n * of the actual call can be trusted to tell you a model is serving.\n *\n * Substrate-free: the caller injects `run`, so this composes with a sandbox\n * turn, a router chat completion, or a test fake without importing any of them.\n */\n\n/**\n * Error codes that mean \"this model's upstream is unavailable — a different\n * model may still work\". Deliberately excludes codes that would fail identically\n * on every model (bad request, auth, content filter): retrying those down a\n * chain burns latency and money to reach the same failure.\n */\nexport const UPSTREAM_UNAVAILABLE_CODES: readonly string[] = [\n 'provider_inference_unavailable',\n 'provider_quota_exhausted',\n 'upstream_unavailable',\n 'insufficient_quota',\n 'model_not_available',\n 'server_error',\n 'bad_gateway',\n 'service_unavailable',\n]\n\n/** HTTP statuses that indicate an upstream capacity/availability problem. */\nexport const UPSTREAM_UNAVAILABLE_STATUSES: readonly number[] = [429, 500, 502, 503, 504]\n\n/**\n * Statuses that mean the REQUEST is wrong, so every model in the chain would\n * fail on it identically. Listed explicitly rather than as \"any 4xx\" because\n * 404 is NOT one of them: a model the endpoint cannot find is model-scoped, and\n * the next model in the chain may well be there. 429 is absent for the same\n * reason in reverse — it is a capacity signal and lives in the list above.\n */\nconst REQUEST_ERROR_STATUSES: readonly number[] = [400, 401, 403, 405, 413, 422]\n\n/**\n * Message fragments emitted by real upstreams during this class of outage.\n * Matched case-insensitively as a last resort, after code and status.\n */\nconst UPSTREAM_UNAVAILABLE_MESSAGES: readonly string[] = [\n 'bad gateway',\n 'service unavailable',\n 'inference temporarily unavailable',\n 'provider inference is unavailable',\n 'insufficient balance',\n 'usage limits',\n 'quota exceeded',\n 'rate limit',\n 'overloaded',\n 'temporarily unavailable',\n // Model-SCOPED unavailability. A different model in the chain can still\n // serve, so this belongs here and not with the client errors. Measured on a\n // real box 2026-07-27: the sandbox now reports an unservable model as a\n // terminal `error` whose data is `{\"message\":\"Session error:\n // {\\\"error\\\":{\\\"name\\\":\\\"UnknownError\\\",\\\"data\\\":{\\\"message\\\":\\\"Model not\n // found: openai-compat/zai/glm-4.7.\\\"}}}\"}` — no `code`, no numeric status,\n // and none of the fragments above. The shipped live product-path proof went\n // red on exactly this: `usedFallback:false`, `failed:true`, an error row to\n // the customer where a fallback was available and would have answered.\n 'model not found',\n 'is not currently available',\n]\n\n/**\n * Textual forms an HTTP status arrives in when NO numeric field carries it.\n *\n * The case that forced this is the opaque Cloudflare 502. Captured verbatim\n * from `router.tangle.tools` on 2026-07-27 17:48:12 GMT (cf-ray\n * `a21d793899f6cef3-DEN`): `HTTP/2 502`, `content-type: text/plain;\n * charset=UTF-8`, a 16-byte body reading `error code: 502\\n`, `server:\n * cloudflare`, and **zero `x-tangle-*` headers** where a healthy 200 from the\n * same endpoint carries nine of them.\n *\n * That capture was originally read as \"zero headers means the origin never\n * executed\". It does not, and the correction matters more than the original\n * guess did. Matching client-observed cf-rays against the origin's own access\n * log (tangle-router #307) showed all 62 of those responses WERE produced by\n * the router — `content-type: application/json`, an `X-Generation-Id`, and a\n * routing trace showing the upstream had returned 200 — and Cloudflare\n * replaced the body and every response header with its own error page, because\n * an origin 502 reads to the edge as the origin having failed.\n *\n * The classifier's BEHAVIOR here was right for the wrong reason, and stays\n * exactly as it is. What changes is why: the rescue is needed not because the\n * origin is unreachable, but because a response the origin carefully\n * structured arrives at the client as sixteen unlabelled bytes. The router now\n * emits 503 instead of 502 on those paths so the body survives, but this\n * pattern must remain — it is the client's only handle on any origin 5xx the\n * edge decides to rewrite, including from a router that has not deployed the\n * fix yet.\n *\n * Every pattern below matches a string a real producer emits:\n * - `error code: 502` — Cloudflare's edge body (the capture above).\n * - `(HTTP 502)` — `src/runtime/openai-stream.ts` wrapping a non-ok response.\n * - `returned 502` — `src/runtime/model-catalog.ts`.\n * - `failed with status 502` — `src/turn-stream/adapters.ts`.\n * - `502 Bad Gateway` — an HTTP/1.1 status line. HTTP/2 carries no reason\n * phrase at all, which is precisely why matching the phrase alone (the\n * `'bad gateway'` fragment below) read the capture as a non-outage.\n */\nconst HTTP_STATUS_HINT_PATTERNS: readonly RegExp[] = [\n /\\berror\\s+code:?\\s*([1-5]\\d{2})\\b/i,\n /\\bhttp(?:\\/[\\d.]+)?[\\s:]\\s*([1-5]\\d{2})\\b/i,\n /\\bstatus(?:\\s*code)?[\\s:=]\\s*([1-5]\\d{2})\\b/i,\n /\\b(?:returned|responded(?:\\s+with)?)\\s+([1-5]\\d{2})\\b/i,\n /\\b([1-5]\\d{2})\\s+(?:bad\\s+gateway|service\\s+unavailable|gateway\\s+time-?out|internal\\s+server\\s+error|too\\s+many\\s+requests)\\b/i,\n]\n\n/**\n * The HTTP status a message states in prose, or `undefined` when it states\n * none. Deliberately NOT \"any three digits in the string\" — an unanchored digit\n * scan would read a token count or a duration as a status.\n */\nexport function readHttpStatusHint(text: string): number | undefined {\n for (const pattern of HTTP_STATUS_HINT_PATTERNS) {\n const match = pattern.exec(text)\n if (match?.[1]) return Number(match[1])\n }\n return undefined\n}\n\nfunction readString(source: Record<string, unknown>, key: string): string | undefined {\n const value = source[key]\n return typeof value === 'string' && value.trim().length > 0 ? value : undefined\n}\n\n/** Read a recognized upstream code, including the terminal message-only shape. */\nexport function readUpstreamUnavailableCode(signal: unknown): string | undefined {\n if (signal === null || typeof signal !== 'object') return undefined\n const record = signal as Record<string, unknown>\n const nested = record.error\n const nestedRecord = nested !== null && typeof nested === 'object' ? (nested as Record<string, unknown>) : undefined\n const code =\n readString(record, 'errorCode') ??\n readString(record, 'code') ??\n (nestedRecord ? (readString(nestedRecord, 'code') ?? readString(nestedRecord, 'type')) : undefined)\n if (code && UPSTREAM_UNAVAILABLE_CODES.includes(code)) return code\n\n const message =\n readString(record, 'message') ??\n readString(record, 'error') ??\n (nestedRecord ? readString(nestedRecord, 'message') : undefined)\n return message && /\\(\\s*provider_quota_exhausted\\s*\\)/i.test(message)\n ? 'provider_quota_exhausted'\n : undefined\n}\n\n/**\n * True when `signal` — a thrown error OR a resolved result payload — indicates\n * the model's upstream is unavailable and another model is worth trying.\n *\n * Checked in order of decreasing confidence: explicit code, HTTP status, then\n * message text. A resolved payload only counts as a failure when it carries an\n * explicit failure marker (`success: false`, or an `error`/`errorCode` field) —\n * a successful result is never misread as an outage.\n */\nexport function isUpstreamUnavailable(signal: unknown): boolean {\n if (signal === null || typeof signal !== 'object') return false\n const record = signal as Record<string, unknown>\n\n // A resolved payload that explicitly reports success is never an outage.\n if (record.success === true) return false\n\n const nested = record.error\n const nestedRecord = nested !== null && typeof nested === 'object' ? (nested as Record<string, unknown>) : undefined\n if (readUpstreamUnavailableCode(record)) return true\n\n for (const key of ['status', 'statusCode', 'httpStatus']) {\n const value = record[key]\n if (typeof value === 'number' && UPSTREAM_UNAVAILABLE_STATUSES.includes(value)) return true\n }\n\n const message =\n readString(record, 'message') ??\n readString(record, 'error') ??\n (nestedRecord ? readString(nestedRecord, 'message') : undefined)\n if (!message) return false\n\n // A status carried as PROSE, checked before the fragment list because it is\n // the stronger signal and it cuts BOTH ways. An edge 502 names no exception\n // type and sets no numeric field, so without this the shipped classifier read\n // `error code: 502` as a non-outage and handed the customer a Bad Gateway\n // instead of trying the next model — the exact failure the router cannot\n // rescue from inside its own origin.\n const hinted = readHttpStatusHint(message)\n if (hinted !== undefined) {\n if (UPSTREAM_UNAVAILABLE_STATUSES.includes(hinted)) return true\n // An explicit REQUEST-error status is decisive the OTHER way. A 400\n // carrying a validation message must surface rather than walk the chain,\n // even when its body happens to contain a word from the fragment list —\n // the router's own `Function tools with reasoning_effort are not supported`\n // 400 is a request-shaping bug that fails identically on every model.\n if (REQUEST_ERROR_STATUSES.includes(hinted)) return false\n }\n\n const lowered = message.toLowerCase()\n return UPSTREAM_UNAVAILABLE_MESSAGES.some((fragment) => lowered.includes(fragment))\n}\n\n/** One model tried, and how it went. */\nexport interface ModelFailoverAttempt {\n model: string\n ok: boolean\n /** Why this model was abandoned. Absent when `ok`. */\n reason?: string\n /** Recognized upstream code carried by this failed attempt. */\n errorCode?: string\n}\n\n/** The outcome of a failover run: the value plus the full attempt trail. */\nexport interface ModelFailoverResult<T> {\n value: T\n /** The model that actually produced `value`. */\n model: string\n attempts: ModelFailoverAttempt[]\n /** True when the preferred (first) model did not serve the request. */\n usedFallback: boolean\n}\n\n/** Every model in the chain failed; carries the trail for logging. */\nexport class ModelFailoverExhaustedError extends Error {\n readonly attempts: ModelFailoverAttempt[]\n /** Last recognized upstream code across the exhausted chain, when present. */\n readonly upstreamCode?: string\n constructor(attempts: ModelFailoverAttempt[]) {\n const trail = attempts.map((a) => `${a.model}: ${a.reason ?? 'failed'}`).join(' | ')\n super(`All ${attempts.length} model(s) failed. ${trail}`)\n this.name = 'ModelFailoverExhaustedError'\n this.attempts = attempts\n this.upstreamCode = [...attempts].reverse().find((attempt) => attempt.errorCode)?.errorCode\n }\n}\n\n/** Inputs to {@link runWithModelFailover}. */\nexport interface RunWithModelFailoverInput<T> {\n /** Preferred model first, then fallbacks in descending preference. */\n models: readonly string[]\n /** Executes one turn with the given model. */\n run: (model: string) => Promise<T>\n /**\n * Classifies a resolved result as an upstream outage. Defaults to\n * {@link isUpstreamUnavailable}, which understands the sandbox's\n * `{ success: false, errorCode }` payload.\n */\n isUnavailableResult?: (result: T) => boolean\n /** Classifies a thrown error. Defaults to {@link isUpstreamUnavailable}. */\n isUnavailableError?: (error: unknown) => boolean\n /** Observability hook fired each time a model is abandoned. */\n onFallback?: (attempt: ModelFailoverAttempt, nextModel: string) => void\n}\n\nfunction describe(signal: unknown): string {\n if (signal instanceof Error) return signal.message\n if (signal !== null && typeof signal === 'object') {\n const record = signal as Record<string, unknown>\n const message = readString(record, 'error') ?? readString(record, 'message') ?? readString(record, 'errorCode')\n if (message) return message\n }\n return String(signal)\n}\n\n/**\n * Run `run` against the first model in `models` that does not report an upstream\n * outage, falling through the chain in order.\n *\n * A non-outage failure (bad request, auth, content filter) is re-thrown\n * immediately rather than retried down the chain — those fail identically on\n * every model, so walking the chain would only multiply latency and spend.\n *\n * @throws ModelFailoverExhaustedError when every model reports an outage.\n */\nexport async function runWithModelFailover<T>(\n input: RunWithModelFailoverInput<T>,\n): Promise<ModelFailoverResult<T>> {\n const models = input.models.map((m) => m.trim()).filter((m) => m.length > 0)\n if (models.length === 0) throw new Error('runWithModelFailover requires at least one model')\n\n const isUnavailableResult = input.isUnavailableResult ?? ((r: T) => isUpstreamUnavailable(r))\n const isUnavailableError = input.isUnavailableError ?? isUpstreamUnavailable\n const attempts: ModelFailoverAttempt[] = []\n\n for (let index = 0; index < models.length; index += 1) {\n const model = models[index]!\n let result: T\n try {\n result = await input.run(model)\n } catch (error) {\n if (!isUnavailableError(error)) throw error\n const errorCode = readUpstreamUnavailableCode(error)\n const attempt: ModelFailoverAttempt = {\n model,\n ok: false,\n reason: describe(error),\n ...(errorCode ? { errorCode } : {}),\n }\n attempts.push(attempt)\n const next = models[index + 1]\n if (next) input.onFallback?.(attempt, next)\n continue\n }\n\n if (isUnavailableResult(result)) {\n const errorCode = readUpstreamUnavailableCode(result)\n const attempt: ModelFailoverAttempt = {\n model,\n ok: false,\n reason: describe(result),\n ...(errorCode ? { errorCode } : {}),\n }\n attempts.push(attempt)\n const next = models[index + 1]\n if (next) input.onFallback?.(attempt, next)\n continue\n }\n\n attempts.push({ model, ok: true })\n return { value: result, model, attempts, usedFallback: index > 0 }\n }\n\n throw new ModelFailoverExhaustedError(attempts)\n}\n\n/**\n * Build a failover chain: the preferred model first, then `fallbacks`, with\n * duplicates removed so a model is never retried twice in one turn.\n */\nexport function buildModelChain(preferred: string, fallbacks: readonly string[]): string[] {\n const chain: string[] = []\n for (const model of [preferred, ...fallbacks]) {\n const cleaned = typeof model === 'string' ? model.trim() : ''\n if (cleaned.length > 0 && !chain.includes(cleaned)) chain.push(cleaned)\n }\n return chain\n}\n"],"mappings":"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","names":[]}
|