@akon-labs/nexus-cli 0.1.1 → 0.1.3
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/CHANGELOG.md +12 -0
- package/README.md +1 -2
- package/dist/cli/require-login.d.ts.map +1 -1
- package/dist/cli/require-login.js +1 -1
- package/dist/cli/require-login.js.map +1 -1
- package/dist/cli.js +404 -133
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/core/auth-guidance.d.ts.map +1 -1
- package/dist/core/auth-guidance.js +3 -2
- package/dist/core/auth-guidance.js.map +1 -1
- package/dist/core/models-dev-catalog.d.ts.map +1 -1
- package/dist/core/models-dev-catalog.js +11 -7
- package/dist/core/models-dev-catalog.js.map +1 -1
- package/dist/core/sdk.d.ts +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/tools/websearch.d.ts.map +1 -1
- package/dist/core/tools/websearch.js +52 -37
- package/dist/core/tools/websearch.js.map +1 -1
- package/dist/extensions/gitnexus/device-auth.d.ts.map +1 -1
- package/dist/extensions/gitnexus/device-auth.js +2 -1
- package/dist/extensions/gitnexus/device-auth.js.map +1 -1
- package/dist/extensions/gitnexus/gateway.d.ts +2 -0
- package/dist/extensions/gitnexus/gateway.d.ts.map +1 -1
- package/dist/extensions/gitnexus/gateway.js +31 -5
- package/dist/extensions/gitnexus/gateway.js.map +1 -1
- package/dist/extensions/gitnexus/index.d.ts.map +1 -1
- package/dist/extensions/gitnexus/index.js +31 -7
- package/dist/extensions/gitnexus/index.js.map +1 -1
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/index.js +2 -0
- package/dist/extensions/index.js.map +1 -1
- package/dist/extensions/web-access.d.ts +14 -0
- package/dist/extensions/web-access.d.ts.map +1 -0
- package/dist/extensions/web-access.js +110 -0
- package/dist/extensions/web-access.js.map +1 -0
- package/dist/extensions/web-search-concurrency.d.ts +14 -0
- package/dist/extensions/web-search-concurrency.d.ts.map +1 -0
- package/dist/extensions/web-search-concurrency.js +93 -0
- package/dist/extensions/web-search-concurrency.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +419 -146
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +10 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/command-palette.d.ts.map +1 -1
- package/dist/modes/interactive/components/command-palette.js +2 -2
- package/dist/modes/interactive/components/command-palette.js.map +1 -1
- package/dist/rpc-entry.js +404 -133
- package/docs/settings.md +1 -1
- package/npm-shrinkwrap.json +3 -3
- package/package.json +2 -2
package/dist/core/sdk.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/core/sdk.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from \"node:path\";\nimport { Agent, type AgentMessage, setDefaultStreamFn, type ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport { clampThinkingLevel, type Message, type Model, streamSimple } from \"@earendil-works/pi-ai/compat\";\nimport { getAgentDir } from \"../config.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { AgentSession } from \"./agent-session.ts\";\nimport { formatNoModelsAvailableMessage } from \"./auth-guidance.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport type { ExtensionRunner, LoadExtensionsResult, SessionStartEvent, ToolDefinition } from \"./extensions/index.ts\";\nimport { convertToLlm } from \"./messages.ts\";\nimport { findInitialModel } from \"./model-resolver.ts\";\nimport { ModelRuntime } from \"./model-runtime.ts\";\nimport { mergeProviderAttributionHeaders } from \"./provider-attribution.ts\";\nimport type { ResourceLoader } from \"./resource-loader.ts\";\nimport { DefaultResourceLoader } from \"./resource-loader.ts\";\nimport { getDefaultSessionDir, SessionManager } from \"./session-manager.ts\";\nimport { SettingsManager } from \"./settings-manager.ts\";\nimport { time } from \"./timings.ts\";\nimport {\n\tcreateBashTool,\n\tcreateCodingTools,\n\tcreateEditTool,\n\tcreateFindTool,\n\tcreateGrepTool,\n\tcreateLsTool,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateWriteTool,\n\ttype ToolName,\n\twithFileMutationQueue,\n} from \"./tools/index.ts\";\n\n// Preserve the pre-0.81 fallback for extensions that construct Agent instances\n// or invoke low-level agent loops without supplying streamFn. Agent core remains\n// provider-agnostic and does not import pi-ai/compat itself.\nsetDefaultStreamFn(streamSimple);\n\nexport interface CreateAgentSessionOptions {\n\t/** Working directory for project-local discovery. Default: process.cwd() */\n\tcwd?: string;\n\t/** Global config directory. Default: ~/.pi/agent */\n\tagentDir?: string;\n\n\t/** Canonical model/auth runtime. Defaults to a runtime using agentDir/auth.json and models.json. */\n\tmodelRuntime?: ModelRuntime;\n\n\t/** Model to use. Default: exact saved setting; never another provider. */\n\tmodel?: Model<any>;\n\t/** Thinking level. Default: from settings, else 'medium' (clamped to model capabilities) */\n\tthinkingLevel?: ThinkingLevel;\n\t/** Models available for cycling (Ctrl+P in interactive mode) */\n\tscopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;\n\n\t/**\n\t * Optional default tool suppression mode when no explicit allowlist is provided.\n\t *\n\t * - \"all\": start with no tools enabled\n\t * - \"builtin\": disable the default built-in tools (read, bash, edit, write)\n\t * but keep extension/custom tools enabled\n\t */\n\tnoTools?: \"all\" | \"builtin\";\n\t/**\n\t * Optional allowlist of tool names.\n\t *\n\t * When omitted, pi enables the default built-in tools (read, bash, edit, write)\n\t * and leaves extension/custom tools enabled unless `noTools` changes that default.\n\t * When provided, only the listed tool names are enabled.\n\t */\n\ttools?: string[];\n\t/** Optional denylist of tool names to disable. Applies after `tools` when both are provided. */\n\texcludeTools?: string[];\n\t/** Custom tools to register (in addition to built-in tools). */\n\tcustomTools?: ToolDefinition[];\n\n\t/** Resource loader. When omitted, DefaultResourceLoader is used. */\n\tresourceLoader?: ResourceLoader;\n\n\t/** Session manager. Default: SessionManager.create(cwd) */\n\tsessionManager?: SessionManager;\n\n\t/** Settings manager. Default: SettingsManager.create(cwd, agentDir) */\n\tsettingsManager?: SettingsManager;\n\t/** Session start event metadata for extension runtime startup. */\n\tsessionStartEvent?: SessionStartEvent;\n}\n\n/** Result from createAgentSession */\nexport interface CreateAgentSessionResult {\n\t/** The created session */\n\tsession: AgentSession;\n\t/** Extensions result (for UI context setup in interactive mode) */\n\textensionsResult: LoadExtensionsResult;\n\t/** Warning if session was restored with a different model than saved */\n\tmodelFallbackMessage?: string;\n}\n\n// Re-exports\n\nexport * from \"./agent-session-runtime.ts\";\nexport type {\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionContext,\n\tExtensionFactory,\n\tInlineExtension,\n\tSlashCommandInfo,\n\tSlashCommandSource,\n\tToolDefinition,\n} from \"./extensions/index.ts\";\nexport type { PromptTemplate } from \"./prompt-templates.ts\";\nexport type { Skill } from \"./skills.ts\";\nexport type { Tool } from \"./tools/index.ts\";\n\nexport {\n\twithFileMutationQueue,\n\t// Tool factories (for custom cwd)\n\tcreateCodingTools,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateBashTool,\n\tcreateEditTool,\n\tcreateWriteTool,\n\tcreateGrepTool,\n\tcreateFindTool,\n\tcreateLsTool,\n};\n\n// Helper Functions\n\nfunction getDefaultAgentDir(): string {\n\treturn getAgentDir();\n}\n\n/**\n * Create an AgentSession with the specified options.\n *\n * @example\n * ```typescript\n * // Minimal - uses defaults\n * const { session } = await createAgentSession();\n *\n * // With explicit model\n * import { getModel } from '@earendil-works/pi-ai';\n * const { session } = await createAgentSession({\n * model: getModel('anthropic', 'claude-opus-4-5'),\n * thinkingLevel: 'high',\n * });\n *\n * // Continue previous session\n * const { session, modelFallbackMessage } = await createAgentSession({\n * continueSession: true,\n * });\n *\n * // Full control\n * const loader = new DefaultResourceLoader({\n * cwd: process.cwd(),\n * agentDir: getAgentDir(),\n * settingsManager: SettingsManager.create(),\n * });\n * await loader.reload();\n * const { session } = await createAgentSession({\n * model: myModel,\n * tools: [\"read\", \"bash\"],\n * resourceLoader: loader,\n * sessionManager: SessionManager.inMemory(),\n * });\n * ```\n */\nexport async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {\n\tconst cwd = resolvePath(options.cwd ?? options.sessionManager?.getCwd() ?? process.cwd());\n\tconst agentDir = options.agentDir ? resolvePath(options.agentDir) : getDefaultAgentDir();\n\tlet resourceLoader = options.resourceLoader;\n\n\tconst authPath = options.agentDir ? join(agentDir, \"auth.json\") : undefined;\n\tconst modelsPath = options.agentDir ? join(agentDir, \"models.json\") : undefined;\n\tconst modelRuntime = options.modelRuntime ?? (await ModelRuntime.create({ authPath, modelsPath }));\n\n\tconst settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);\n\tconst sessionManager = options.sessionManager ?? SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir));\n\n\tif (!resourceLoader) {\n\t\tresourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager });\n\t\tawait resourceLoader.reload();\n\t\ttime(\"resourceLoader.reload\");\n\t}\n\n\t// Check if session has existing data to restore\n\tconst existingSession = sessionManager.buildSessionContext();\n\tconst hasExistingSession = existingSession.messages.length > 0;\n\tconst hasThinkingEntry = sessionManager.getBranch().some((entry) => entry.type === \"thinking_level_change\");\n\n\tlet model = options.model;\n\tlet modelFallbackMessage: string | undefined;\n\n\t// If session has data, try to restore model from it\n\tif (!model && hasExistingSession && existingSession.model) {\n\t\tconst restoredModel = modelRuntime.getModel(existingSession.model.provider, existingSession.model.modelId);\n\t\tif (restoredModel) {\n\t\t\tmodel = restoredModel;\n\t\t}\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = `Could not restore model ${existingSession.model.provider}/${existingSession.model.modelId}`;\n\t\t}\n\t}\n\n\t// If still no model, use only the exact saved default. Nexus never switches providers automatically.\n\tif (!model) {\n\t\tconst result = await findInitialModel({\n\t\t\tscopedModels: [],\n\t\t\tisContinuing: hasExistingSession,\n\t\t\tdefaultProvider: settingsManager.getDefaultProvider(),\n\t\t\tdefaultModelId: settingsManager.getDefaultModel(),\n\t\t\tdefaultThinkingLevel: settingsManager.getDefaultThinkingLevel(),\n\t\t\tmodelRuntime,\n\t\t});\n\t\tmodel = result.model;\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = formatNoModelsAvailableMessage();\n\t\t} else if (modelFallbackMessage) {\n\t\t\tmodelFallbackMessage += `. Using ${model.provider}/${model.id}`;\n\t\t}\n\t}\n\n\tlet thinkingLevel = options.thinkingLevel;\n\n\t// If session has data, restore thinking level from it\n\tif (thinkingLevel === undefined && hasExistingSession) {\n\t\tthinkingLevel = hasThinkingEntry\n\t\t\t? (existingSession.thinkingLevel as ThinkingLevel)\n\t\t\t: (settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL);\n\t}\n\n\t// Fall back to settings default\n\tif (thinkingLevel === undefined) {\n\t\tthinkingLevel = settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;\n\t}\n\n\t// Clamp to model capabilities\n\tif (!model) {\n\t\tthinkingLevel = \"off\";\n\t} else {\n\t\tthinkingLevel = clampThinkingLevel(model, thinkingLevel) as ThinkingLevel;\n\t}\n\n\tconst defaultActiveToolNames: ToolName[] = [\"read\", \"bash\", \"edit\", \"write\", \"websearch\"];\n\tconst allowedToolNames = options.tools ?? (options.noTools === \"all\" ? [] : undefined);\n\tconst excludedToolNames = options.excludeTools;\n\tconst excludedToolNameSet = excludedToolNames ? new Set(excludedToolNames) : undefined;\n\tconst initialActiveToolNames: string[] = (\n\t\toptions.tools ? [...options.tools] : options.noTools ? [] : defaultActiveToolNames\n\t).filter((name) => !excludedToolNameSet?.has(name));\n\n\tlet agent: Agent;\n\n\t// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)\n\tconst convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {\n\t\tconst converted = convertToLlm(messages);\n\t\t// Check setting dynamically so mid-session changes take effect\n\t\tif (!settingsManager.getBlockImages()) {\n\t\t\treturn converted;\n\t\t}\n\t\t// Filter out ImageContent from all messages, replacing with text placeholder\n\t\treturn converted.map((msg) => {\n\t\t\tif (msg.role === \"user\" || msg.role === \"toolResult\") {\n\t\t\t\tconst content = msg.content;\n\t\t\t\tif (Array.isArray(content)) {\n\t\t\t\t\tconst hasImages = content.some((c) => c.type === \"image\");\n\t\t\t\t\tif (hasImages) {\n\t\t\t\t\t\tconst filteredContent = content\n\t\t\t\t\t\t\t.map((c) =>\n\t\t\t\t\t\t\t\tc.type === \"image\" ? { type: \"text\" as const, text: \"Image reading is disabled.\" } : c,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.filter(\n\t\t\t\t\t\t\t\t(c, i, arr) =>\n\t\t\t\t\t\t\t\t\t// Dedupe consecutive \"Image reading is disabled.\" texts\n\t\t\t\t\t\t\t\t\t!(\n\t\t\t\t\t\t\t\t\t\tc.type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\tc.text === \"Image reading is disabled.\" &&\n\t\t\t\t\t\t\t\t\t\ti > 0 &&\n\t\t\t\t\t\t\t\t\t\tarr[i - 1].type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\t(arr[i - 1] as { type: \"text\"; text: string }).text === \"Image reading is disabled.\"\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\treturn { ...msg, content: filteredContent };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn msg;\n\t\t});\n\t};\n\n\tconst extensionRunnerRef: { current?: ExtensionRunner } = {};\n\n\tagent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt: \"\",\n\t\t\tmodel,\n\t\t\tthinkingLevel,\n\t\t\ttools: [],\n\t\t},\n\t\tconvertToLlm: convertToLlmWithBlockImages,\n\t\tstreamFn: async (model, context, options) => {\n\t\t\tconst providerRetrySettings = settingsManager.getProviderRetrySettings();\n\t\t\tconst httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs();\n\t\t\t// SDKs treat timeout=0 as 0ms (immediate timeout), not \"no timeout\".\n\t\t\t// Use max int32 to effectively disable the timeout.\n\t\t\tconst effectiveTimeoutMs = httpIdleTimeoutMs === 0 ? 2147483647 : httpIdleTimeoutMs;\n\t\t\tconst timeoutMs = options?.timeoutMs ?? providerRetrySettings.timeoutMs ?? effectiveTimeoutMs;\n\t\t\tconst websocketConnectTimeoutMs =\n\t\t\t\toptions?.websocketConnectTimeoutMs ?? settingsManager.getWebSocketConnectTimeoutMs();\n\t\t\tconst headerRunner = extensionRunnerRef.current;\n\t\t\treturn modelRuntime.streamSimple(model, context, {\n\t\t\t\t...options,\n\t\t\t\ttimeoutMs,\n\t\t\t\twebsocketConnectTimeoutMs,\n\t\t\t\tmaxRetries: options?.maxRetries ?? providerRetrySettings.maxRetries,\n\t\t\t\tmaxRetryDelayMs: options?.maxRetryDelayMs ?? providerRetrySettings.maxRetryDelayMs,\n\t\t\t\ttransformHeaders: async (requestHeaders) => {\n\t\t\t\t\tconst headers = mergeProviderAttributionHeaders(\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tsettingsManager,\n\t\t\t\t\t\toptions?.sessionId,\n\t\t\t\t\t\trequestHeaders,\n\t\t\t\t\t);\n\t\t\t\t\treturn headerRunner?.hasHandlers(\"before_provider_headers\")\n\t\t\t\t\t\t? headerRunner.emitBeforeProviderHeaders(headers ?? {})\n\t\t\t\t\t\t: (headers ?? {});\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t\tonPayload: async (payload, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"before_provider_request\")) {\n\t\t\t\treturn payload;\n\t\t\t}\n\t\t\treturn runner.emitBeforeProviderRequest(payload);\n\t\t},\n\t\tonResponse: async (response, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"after_provider_response\")) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait runner.emit({\n\t\t\t\ttype: \"after_provider_response\",\n\t\t\t\tstatus: response.status,\n\t\t\t\theaders: response.headers,\n\t\t\t});\n\t\t},\n\t\tsessionId: sessionManager.getSessionId(),\n\t\ttransformContext: async (messages) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner) return messages;\n\t\t\treturn runner.emitContext(messages);\n\t\t},\n\t\tsteeringMode: settingsManager.getSteeringMode(),\n\t\tfollowUpMode: settingsManager.getFollowUpMode(),\n\t\ttransport: settingsManager.getTransport(),\n\t\tthinkingBudgets: settingsManager.getThinkingBudgets(),\n\t\tmaxRetryDelayMs: settingsManager.getProviderRetrySettings().maxRetryDelayMs,\n\t});\n\n\t// Restore messages if session has existing data\n\tif (hasExistingSession) {\n\t\tagent.state.messages = existingSession.messages;\n\t\tif (!hasThinkingEntry) {\n\t\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t\t}\n\t} else {\n\t\t// Save initial model and thinking level for new sessions so they can be restored on resume\n\t\tif (model) {\n\t\t\tsessionManager.appendModelChange(model.provider, model.id);\n\t\t}\n\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t}\n\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager,\n\t\tcwd,\n\t\tscopedModels: options.scopedModels,\n\t\tresourceLoader,\n\t\tcustomTools: options.customTools,\n\t\tmodelRuntime,\n\t\tinitialActiveToolNames,\n\t\tallowedToolNames,\n\t\texcludedToolNames,\n\t\textensionRunnerRef,\n\t\tsessionStartEvent: options.sessionStartEvent,\n\t});\n\tconst extensionsResult = resourceLoader.getExtensions();\n\n\treturn {\n\t\tsession,\n\t\textensionsResult,\n\t\tmodelFallbackMessage,\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/core/sdk.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from \"node:path\";\nimport { Agent, type AgentMessage, setDefaultStreamFn, type ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport { clampThinkingLevel, type Message, type Model, streamSimple } from \"@earendil-works/pi-ai/compat\";\nimport { getAgentDir } from \"../config.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { AgentSession } from \"./agent-session.ts\";\nimport { formatNoModelsAvailableMessage } from \"./auth-guidance.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport type { ExtensionRunner, LoadExtensionsResult, SessionStartEvent, ToolDefinition } from \"./extensions/index.ts\";\nimport { convertToLlm } from \"./messages.ts\";\nimport { findInitialModel } from \"./model-resolver.ts\";\nimport { ModelRuntime } from \"./model-runtime.ts\";\nimport { mergeProviderAttributionHeaders } from \"./provider-attribution.ts\";\nimport type { ResourceLoader } from \"./resource-loader.ts\";\nimport { DefaultResourceLoader } from \"./resource-loader.ts\";\nimport { getDefaultSessionDir, SessionManager } from \"./session-manager.ts\";\nimport { SettingsManager } from \"./settings-manager.ts\";\nimport { time } from \"./timings.ts\";\nimport {\n\tcreateBashTool,\n\tcreateCodingTools,\n\tcreateEditTool,\n\tcreateFindTool,\n\tcreateGrepTool,\n\tcreateLsTool,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateWriteTool,\n\ttype ToolName,\n\twithFileMutationQueue,\n} from \"./tools/index.ts\";\n\n// Preserve the pre-0.81 fallback for extensions that construct Agent instances\n// or invoke low-level agent loops without supplying streamFn. Agent core remains\n// provider-agnostic and does not import pi-ai/compat itself.\nsetDefaultStreamFn(streamSimple);\n\nexport interface CreateAgentSessionOptions {\n\t/** Working directory for project-local discovery. Default: process.cwd() */\n\tcwd?: string;\n\t/** Global config directory. Default: ~/.pi/agent */\n\tagentDir?: string;\n\n\t/** Canonical model/auth runtime. Defaults to a runtime using agentDir/auth.json and models.json. */\n\tmodelRuntime?: ModelRuntime;\n\n\t/** Model to use. Default: exact saved setting; otherwise entitled gitnexus/auto when available. */\n\tmodel?: Model<any>;\n\t/** Thinking level. Default: from settings, else 'medium' (clamped to model capabilities) */\n\tthinkingLevel?: ThinkingLevel;\n\t/** Models available for cycling (Ctrl+P in interactive mode) */\n\tscopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;\n\n\t/**\n\t * Optional default tool suppression mode when no explicit allowlist is provided.\n\t *\n\t * - \"all\": start with no tools enabled\n\t * - \"builtin\": disable the default built-in tools (read, bash, edit, write)\n\t * but keep extension/custom tools enabled\n\t */\n\tnoTools?: \"all\" | \"builtin\";\n\t/**\n\t * Optional allowlist of tool names.\n\t *\n\t * When omitted, pi enables the default built-in tools (read, bash, edit, write)\n\t * and leaves extension/custom tools enabled unless `noTools` changes that default.\n\t * When provided, only the listed tool names are enabled.\n\t */\n\ttools?: string[];\n\t/** Optional denylist of tool names to disable. Applies after `tools` when both are provided. */\n\texcludeTools?: string[];\n\t/** Custom tools to register (in addition to built-in tools). */\n\tcustomTools?: ToolDefinition[];\n\n\t/** Resource loader. When omitted, DefaultResourceLoader is used. */\n\tresourceLoader?: ResourceLoader;\n\n\t/** Session manager. Default: SessionManager.create(cwd) */\n\tsessionManager?: SessionManager;\n\n\t/** Settings manager. Default: SettingsManager.create(cwd, agentDir) */\n\tsettingsManager?: SettingsManager;\n\t/** Session start event metadata for extension runtime startup. */\n\tsessionStartEvent?: SessionStartEvent;\n}\n\n/** Result from createAgentSession */\nexport interface CreateAgentSessionResult {\n\t/** The created session */\n\tsession: AgentSession;\n\t/** Extensions result (for UI context setup in interactive mode) */\n\textensionsResult: LoadExtensionsResult;\n\t/** Warning if session was restored with a different model than saved */\n\tmodelFallbackMessage?: string;\n}\n\n// Re-exports\n\nexport * from \"./agent-session-runtime.ts\";\nexport type {\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionContext,\n\tExtensionFactory,\n\tInlineExtension,\n\tSlashCommandInfo,\n\tSlashCommandSource,\n\tToolDefinition,\n} from \"./extensions/index.ts\";\nexport type { PromptTemplate } from \"./prompt-templates.ts\";\nexport type { Skill } from \"./skills.ts\";\nexport type { Tool } from \"./tools/index.ts\";\n\nexport {\n\twithFileMutationQueue,\n\t// Tool factories (for custom cwd)\n\tcreateCodingTools,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateBashTool,\n\tcreateEditTool,\n\tcreateWriteTool,\n\tcreateGrepTool,\n\tcreateFindTool,\n\tcreateLsTool,\n};\n\n// Helper Functions\n\nfunction getDefaultAgentDir(): string {\n\treturn getAgentDir();\n}\n\n/**\n * Create an AgentSession with the specified options.\n *\n * @example\n * ```typescript\n * // Minimal - uses defaults\n * const { session } = await createAgentSession();\n *\n * // With explicit model\n * import { getModel } from '@earendil-works/pi-ai';\n * const { session } = await createAgentSession({\n * model: getModel('anthropic', 'claude-opus-4-5'),\n * thinkingLevel: 'high',\n * });\n *\n * // Continue previous session\n * const { session, modelFallbackMessage } = await createAgentSession({\n * continueSession: true,\n * });\n *\n * // Full control\n * const loader = new DefaultResourceLoader({\n * cwd: process.cwd(),\n * agentDir: getAgentDir(),\n * settingsManager: SettingsManager.create(),\n * });\n * await loader.reload();\n * const { session } = await createAgentSession({\n * model: myModel,\n * tools: [\"read\", \"bash\"],\n * resourceLoader: loader,\n * sessionManager: SessionManager.inMemory(),\n * });\n * ```\n */\nexport async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {\n\tconst cwd = resolvePath(options.cwd ?? options.sessionManager?.getCwd() ?? process.cwd());\n\tconst agentDir = options.agentDir ? resolvePath(options.agentDir) : getDefaultAgentDir();\n\tlet resourceLoader = options.resourceLoader;\n\n\tconst authPath = options.agentDir ? join(agentDir, \"auth.json\") : undefined;\n\tconst modelsPath = options.agentDir ? join(agentDir, \"models.json\") : undefined;\n\tconst modelRuntime = options.modelRuntime ?? (await ModelRuntime.create({ authPath, modelsPath }));\n\n\tconst settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);\n\tconst sessionManager = options.sessionManager ?? SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir));\n\n\tif (!resourceLoader) {\n\t\tresourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager });\n\t\tawait resourceLoader.reload();\n\t\ttime(\"resourceLoader.reload\");\n\t}\n\n\t// Check if session has existing data to restore\n\tconst existingSession = sessionManager.buildSessionContext();\n\tconst hasExistingSession = existingSession.messages.length > 0;\n\tconst hasThinkingEntry = sessionManager.getBranch().some((entry) => entry.type === \"thinking_level_change\");\n\n\tlet model = options.model;\n\tlet modelFallbackMessage: string | undefined;\n\n\t// If session has data, try to restore model from it\n\tif (!model && hasExistingSession && existingSession.model) {\n\t\tconst restoredModel = modelRuntime.getModel(existingSession.model.provider, existingSession.model.modelId);\n\t\tif (restoredModel) {\n\t\t\tmodel = restoredModel;\n\t\t}\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = `Could not restore model ${existingSession.model.provider}/${existingSession.model.modelId}`;\n\t\t}\n\t}\n\n\t// If still no model, use the exact saved default or entitled hosted Auto.\n\tif (!model) {\n\t\tconst result = await findInitialModel({\n\t\t\tscopedModels: [],\n\t\t\tisContinuing: hasExistingSession,\n\t\t\tdefaultProvider: settingsManager.getDefaultProvider(),\n\t\t\tdefaultModelId: settingsManager.getDefaultModel(),\n\t\t\tdefaultThinkingLevel: settingsManager.getDefaultThinkingLevel(),\n\t\t\tmodelRuntime,\n\t\t});\n\t\tmodel = result.model;\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = formatNoModelsAvailableMessage();\n\t\t} else if (modelFallbackMessage) {\n\t\t\tmodelFallbackMessage += `. Using ${model.provider}/${model.id}`;\n\t\t}\n\t}\n\n\tlet thinkingLevel = options.thinkingLevel;\n\n\t// If session has data, restore thinking level from it\n\tif (thinkingLevel === undefined && hasExistingSession) {\n\t\tthinkingLevel = hasThinkingEntry\n\t\t\t? (existingSession.thinkingLevel as ThinkingLevel)\n\t\t\t: (settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL);\n\t}\n\n\t// Fall back to settings default\n\tif (thinkingLevel === undefined) {\n\t\tthinkingLevel = settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;\n\t}\n\n\t// Clamp to model capabilities\n\tif (!model) {\n\t\tthinkingLevel = \"off\";\n\t} else {\n\t\tthinkingLevel = clampThinkingLevel(model, thinkingLevel) as ThinkingLevel;\n\t}\n\n\tconst defaultActiveToolNames: ToolName[] = [\"read\", \"bash\", \"edit\", \"write\", \"websearch\"];\n\tconst allowedToolNames = options.tools ?? (options.noTools === \"all\" ? [] : undefined);\n\tconst excludedToolNames = options.excludeTools;\n\tconst excludedToolNameSet = excludedToolNames ? new Set(excludedToolNames) : undefined;\n\tconst initialActiveToolNames: string[] = (\n\t\toptions.tools ? [...options.tools] : options.noTools ? [] : defaultActiveToolNames\n\t).filter((name) => !excludedToolNameSet?.has(name));\n\n\tlet agent: Agent;\n\n\t// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)\n\tconst convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {\n\t\tconst converted = convertToLlm(messages);\n\t\t// Check setting dynamically so mid-session changes take effect\n\t\tif (!settingsManager.getBlockImages()) {\n\t\t\treturn converted;\n\t\t}\n\t\t// Filter out ImageContent from all messages, replacing with text placeholder\n\t\treturn converted.map((msg) => {\n\t\t\tif (msg.role === \"user\" || msg.role === \"toolResult\") {\n\t\t\t\tconst content = msg.content;\n\t\t\t\tif (Array.isArray(content)) {\n\t\t\t\t\tconst hasImages = content.some((c) => c.type === \"image\");\n\t\t\t\t\tif (hasImages) {\n\t\t\t\t\t\tconst filteredContent = content\n\t\t\t\t\t\t\t.map((c) =>\n\t\t\t\t\t\t\t\tc.type === \"image\" ? { type: \"text\" as const, text: \"Image reading is disabled.\" } : c,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.filter(\n\t\t\t\t\t\t\t\t(c, i, arr) =>\n\t\t\t\t\t\t\t\t\t// Dedupe consecutive \"Image reading is disabled.\" texts\n\t\t\t\t\t\t\t\t\t!(\n\t\t\t\t\t\t\t\t\t\tc.type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\tc.text === \"Image reading is disabled.\" &&\n\t\t\t\t\t\t\t\t\t\ti > 0 &&\n\t\t\t\t\t\t\t\t\t\tarr[i - 1].type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\t(arr[i - 1] as { type: \"text\"; text: string }).text === \"Image reading is disabled.\"\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\treturn { ...msg, content: filteredContent };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn msg;\n\t\t});\n\t};\n\n\tconst extensionRunnerRef: { current?: ExtensionRunner } = {};\n\n\tagent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt: \"\",\n\t\t\tmodel,\n\t\t\tthinkingLevel,\n\t\t\ttools: [],\n\t\t},\n\t\tconvertToLlm: convertToLlmWithBlockImages,\n\t\tstreamFn: async (model, context, options) => {\n\t\t\tconst providerRetrySettings = settingsManager.getProviderRetrySettings();\n\t\t\tconst httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs();\n\t\t\t// SDKs treat timeout=0 as 0ms (immediate timeout), not \"no timeout\".\n\t\t\t// Use max int32 to effectively disable the timeout.\n\t\t\tconst effectiveTimeoutMs = httpIdleTimeoutMs === 0 ? 2147483647 : httpIdleTimeoutMs;\n\t\t\tconst timeoutMs = options?.timeoutMs ?? providerRetrySettings.timeoutMs ?? effectiveTimeoutMs;\n\t\t\tconst websocketConnectTimeoutMs =\n\t\t\t\toptions?.websocketConnectTimeoutMs ?? settingsManager.getWebSocketConnectTimeoutMs();\n\t\t\tconst headerRunner = extensionRunnerRef.current;\n\t\t\treturn modelRuntime.streamSimple(model, context, {\n\t\t\t\t...options,\n\t\t\t\ttimeoutMs,\n\t\t\t\twebsocketConnectTimeoutMs,\n\t\t\t\tmaxRetries: options?.maxRetries ?? providerRetrySettings.maxRetries,\n\t\t\t\tmaxRetryDelayMs: options?.maxRetryDelayMs ?? providerRetrySettings.maxRetryDelayMs,\n\t\t\t\ttransformHeaders: async (requestHeaders) => {\n\t\t\t\t\tconst headers = mergeProviderAttributionHeaders(\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tsettingsManager,\n\t\t\t\t\t\toptions?.sessionId,\n\t\t\t\t\t\trequestHeaders,\n\t\t\t\t\t);\n\t\t\t\t\treturn headerRunner?.hasHandlers(\"before_provider_headers\")\n\t\t\t\t\t\t? headerRunner.emitBeforeProviderHeaders(headers ?? {})\n\t\t\t\t\t\t: (headers ?? {});\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t\tonPayload: async (payload, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"before_provider_request\")) {\n\t\t\t\treturn payload;\n\t\t\t}\n\t\t\treturn runner.emitBeforeProviderRequest(payload);\n\t\t},\n\t\tonResponse: async (response, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"after_provider_response\")) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait runner.emit({\n\t\t\t\ttype: \"after_provider_response\",\n\t\t\t\tstatus: response.status,\n\t\t\t\theaders: response.headers,\n\t\t\t});\n\t\t},\n\t\tsessionId: sessionManager.getSessionId(),\n\t\ttransformContext: async (messages) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner) return messages;\n\t\t\treturn runner.emitContext(messages);\n\t\t},\n\t\tsteeringMode: settingsManager.getSteeringMode(),\n\t\tfollowUpMode: settingsManager.getFollowUpMode(),\n\t\ttransport: settingsManager.getTransport(),\n\t\tthinkingBudgets: settingsManager.getThinkingBudgets(),\n\t\tmaxRetryDelayMs: settingsManager.getProviderRetrySettings().maxRetryDelayMs,\n\t});\n\n\t// Restore messages if session has existing data\n\tif (hasExistingSession) {\n\t\tagent.state.messages = existingSession.messages;\n\t\tif (!hasThinkingEntry) {\n\t\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t\t}\n\t} else {\n\t\t// Save initial model and thinking level for new sessions so they can be restored on resume\n\t\tif (model) {\n\t\t\tsessionManager.appendModelChange(model.provider, model.id);\n\t\t}\n\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t}\n\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager,\n\t\tcwd,\n\t\tscopedModels: options.scopedModels,\n\t\tresourceLoader,\n\t\tcustomTools: options.customTools,\n\t\tmodelRuntime,\n\t\tinitialActiveToolNames,\n\t\tallowedToolNames,\n\t\texcludedToolNames,\n\t\textensionRunnerRef,\n\t\tsessionStartEvent: options.sessionStartEvent,\n\t});\n\tconst extensionsResult = resourceLoader.getExtensions();\n\n\treturn {\n\t\tsession,\n\t\textensionsResult,\n\t\tmodelFallbackMessage,\n\t};\n}\n"]}
|
package/dist/core/sdk.js
CHANGED
|
@@ -93,7 +93,7 @@ export async function createAgentSession(options = {}) {
|
|
|
93
93
|
modelFallbackMessage = `Could not restore model ${existingSession.model.provider}/${existingSession.model.modelId}`;
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
-
// If still no model, use
|
|
96
|
+
// If still no model, use the exact saved default or entitled hosted Auto.
|
|
97
97
|
if (!model) {
|
|
98
98
|
const result = await findInitialModel({
|
|
99
99
|
scopedModels: [],
|
package/dist/core/sdk.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk.js","sourceRoot":"","sources":["../../src/core/sdk.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from \"node:path\";\nimport { Agent, type AgentMessage, setDefaultStreamFn, type ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport { clampThinkingLevel, type Message, type Model, streamSimple } from \"@earendil-works/pi-ai/compat\";\nimport { getAgentDir } from \"../config.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { AgentSession } from \"./agent-session.ts\";\nimport { formatNoModelsAvailableMessage } from \"./auth-guidance.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport type { ExtensionRunner, LoadExtensionsResult, SessionStartEvent, ToolDefinition } from \"./extensions/index.ts\";\nimport { convertToLlm } from \"./messages.ts\";\nimport { findInitialModel } from \"./model-resolver.ts\";\nimport { ModelRuntime } from \"./model-runtime.ts\";\nimport { mergeProviderAttributionHeaders } from \"./provider-attribution.ts\";\nimport type { ResourceLoader } from \"./resource-loader.ts\";\nimport { DefaultResourceLoader } from \"./resource-loader.ts\";\nimport { getDefaultSessionDir, SessionManager } from \"./session-manager.ts\";\nimport { SettingsManager } from \"./settings-manager.ts\";\nimport { time } from \"./timings.ts\";\nimport {\n\tcreateBashTool,\n\tcreateCodingTools,\n\tcreateEditTool,\n\tcreateFindTool,\n\tcreateGrepTool,\n\tcreateLsTool,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateWriteTool,\n\ttype ToolName,\n\twithFileMutationQueue,\n} from \"./tools/index.ts\";\n\n// Preserve the pre-0.81 fallback for extensions that construct Agent instances\n// or invoke low-level agent loops without supplying streamFn. Agent core remains\n// provider-agnostic and does not import pi-ai/compat itself.\nsetDefaultStreamFn(streamSimple);\n\nexport interface CreateAgentSessionOptions {\n\t/** Working directory for project-local discovery. Default: process.cwd() */\n\tcwd?: string;\n\t/** Global config directory. Default: ~/.pi/agent */\n\tagentDir?: string;\n\n\t/** Canonical model/auth runtime. Defaults to a runtime using agentDir/auth.json and models.json. */\n\tmodelRuntime?: ModelRuntime;\n\n\t/** Model to use. Default: exact saved setting; never another provider. */\n\tmodel?: Model<any>;\n\t/** Thinking level. Default: from settings, else 'medium' (clamped to model capabilities) */\n\tthinkingLevel?: ThinkingLevel;\n\t/** Models available for cycling (Ctrl+P in interactive mode) */\n\tscopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;\n\n\t/**\n\t * Optional default tool suppression mode when no explicit allowlist is provided.\n\t *\n\t * - \"all\": start with no tools enabled\n\t * - \"builtin\": disable the default built-in tools (read, bash, edit, write)\n\t * but keep extension/custom tools enabled\n\t */\n\tnoTools?: \"all\" | \"builtin\";\n\t/**\n\t * Optional allowlist of tool names.\n\t *\n\t * When omitted, pi enables the default built-in tools (read, bash, edit, write)\n\t * and leaves extension/custom tools enabled unless `noTools` changes that default.\n\t * When provided, only the listed tool names are enabled.\n\t */\n\ttools?: string[];\n\t/** Optional denylist of tool names to disable. Applies after `tools` when both are provided. */\n\texcludeTools?: string[];\n\t/** Custom tools to register (in addition to built-in tools). */\n\tcustomTools?: ToolDefinition[];\n\n\t/** Resource loader. When omitted, DefaultResourceLoader is used. */\n\tresourceLoader?: ResourceLoader;\n\n\t/** Session manager. Default: SessionManager.create(cwd) */\n\tsessionManager?: SessionManager;\n\n\t/** Settings manager. Default: SettingsManager.create(cwd, agentDir) */\n\tsettingsManager?: SettingsManager;\n\t/** Session start event metadata for extension runtime startup. */\n\tsessionStartEvent?: SessionStartEvent;\n}\n\n/** Result from createAgentSession */\nexport interface CreateAgentSessionResult {\n\t/** The created session */\n\tsession: AgentSession;\n\t/** Extensions result (for UI context setup in interactive mode) */\n\textensionsResult: LoadExtensionsResult;\n\t/** Warning if session was restored with a different model than saved */\n\tmodelFallbackMessage?: string;\n}\n\n// Re-exports\n\nexport * from \"./agent-session-runtime.ts\";\nexport type {\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionContext,\n\tExtensionFactory,\n\tInlineExtension,\n\tSlashCommandInfo,\n\tSlashCommandSource,\n\tToolDefinition,\n} from \"./extensions/index.ts\";\nexport type { PromptTemplate } from \"./prompt-templates.ts\";\nexport type { Skill } from \"./skills.ts\";\nexport type { Tool } from \"./tools/index.ts\";\n\nexport {\n\twithFileMutationQueue,\n\t// Tool factories (for custom cwd)\n\tcreateCodingTools,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateBashTool,\n\tcreateEditTool,\n\tcreateWriteTool,\n\tcreateGrepTool,\n\tcreateFindTool,\n\tcreateLsTool,\n};\n\n// Helper Functions\n\nfunction getDefaultAgentDir(): string {\n\treturn getAgentDir();\n}\n\n/**\n * Create an AgentSession with the specified options.\n *\n * @example\n * ```typescript\n * // Minimal - uses defaults\n * const { session } = await createAgentSession();\n *\n * // With explicit model\n * import { getModel } from '@earendil-works/pi-ai';\n * const { session } = await createAgentSession({\n * model: getModel('anthropic', 'claude-opus-4-5'),\n * thinkingLevel: 'high',\n * });\n *\n * // Continue previous session\n * const { session, modelFallbackMessage } = await createAgentSession({\n * continueSession: true,\n * });\n *\n * // Full control\n * const loader = new DefaultResourceLoader({\n * cwd: process.cwd(),\n * agentDir: getAgentDir(),\n * settingsManager: SettingsManager.create(),\n * });\n * await loader.reload();\n * const { session } = await createAgentSession({\n * model: myModel,\n * tools: [\"read\", \"bash\"],\n * resourceLoader: loader,\n * sessionManager: SessionManager.inMemory(),\n * });\n * ```\n */\nexport async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {\n\tconst cwd = resolvePath(options.cwd ?? options.sessionManager?.getCwd() ?? process.cwd());\n\tconst agentDir = options.agentDir ? resolvePath(options.agentDir) : getDefaultAgentDir();\n\tlet resourceLoader = options.resourceLoader;\n\n\tconst authPath = options.agentDir ? join(agentDir, \"auth.json\") : undefined;\n\tconst modelsPath = options.agentDir ? join(agentDir, \"models.json\") : undefined;\n\tconst modelRuntime = options.modelRuntime ?? (await ModelRuntime.create({ authPath, modelsPath }));\n\n\tconst settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);\n\tconst sessionManager = options.sessionManager ?? SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir));\n\n\tif (!resourceLoader) {\n\t\tresourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager });\n\t\tawait resourceLoader.reload();\n\t\ttime(\"resourceLoader.reload\");\n\t}\n\n\t// Check if session has existing data to restore\n\tconst existingSession = sessionManager.buildSessionContext();\n\tconst hasExistingSession = existingSession.messages.length > 0;\n\tconst hasThinkingEntry = sessionManager.getBranch().some((entry) => entry.type === \"thinking_level_change\");\n\n\tlet model = options.model;\n\tlet modelFallbackMessage: string | undefined;\n\n\t// If session has data, try to restore model from it\n\tif (!model && hasExistingSession && existingSession.model) {\n\t\tconst restoredModel = modelRuntime.getModel(existingSession.model.provider, existingSession.model.modelId);\n\t\tif (restoredModel) {\n\t\t\tmodel = restoredModel;\n\t\t}\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = `Could not restore model ${existingSession.model.provider}/${existingSession.model.modelId}`;\n\t\t}\n\t}\n\n\t// If still no model, use only the exact saved default. Nexus never switches providers automatically.\n\tif (!model) {\n\t\tconst result = await findInitialModel({\n\t\t\tscopedModels: [],\n\t\t\tisContinuing: hasExistingSession,\n\t\t\tdefaultProvider: settingsManager.getDefaultProvider(),\n\t\t\tdefaultModelId: settingsManager.getDefaultModel(),\n\t\t\tdefaultThinkingLevel: settingsManager.getDefaultThinkingLevel(),\n\t\t\tmodelRuntime,\n\t\t});\n\t\tmodel = result.model;\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = formatNoModelsAvailableMessage();\n\t\t} else if (modelFallbackMessage) {\n\t\t\tmodelFallbackMessage += `. Using ${model.provider}/${model.id}`;\n\t\t}\n\t}\n\n\tlet thinkingLevel = options.thinkingLevel;\n\n\t// If session has data, restore thinking level from it\n\tif (thinkingLevel === undefined && hasExistingSession) {\n\t\tthinkingLevel = hasThinkingEntry\n\t\t\t? (existingSession.thinkingLevel as ThinkingLevel)\n\t\t\t: (settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL);\n\t}\n\n\t// Fall back to settings default\n\tif (thinkingLevel === undefined) {\n\t\tthinkingLevel = settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;\n\t}\n\n\t// Clamp to model capabilities\n\tif (!model) {\n\t\tthinkingLevel = \"off\";\n\t} else {\n\t\tthinkingLevel = clampThinkingLevel(model, thinkingLevel) as ThinkingLevel;\n\t}\n\n\tconst defaultActiveToolNames: ToolName[] = [\"read\", \"bash\", \"edit\", \"write\", \"websearch\"];\n\tconst allowedToolNames = options.tools ?? (options.noTools === \"all\" ? [] : undefined);\n\tconst excludedToolNames = options.excludeTools;\n\tconst excludedToolNameSet = excludedToolNames ? new Set(excludedToolNames) : undefined;\n\tconst initialActiveToolNames: string[] = (\n\t\toptions.tools ? [...options.tools] : options.noTools ? [] : defaultActiveToolNames\n\t).filter((name) => !excludedToolNameSet?.has(name));\n\n\tlet agent: Agent;\n\n\t// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)\n\tconst convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {\n\t\tconst converted = convertToLlm(messages);\n\t\t// Check setting dynamically so mid-session changes take effect\n\t\tif (!settingsManager.getBlockImages()) {\n\t\t\treturn converted;\n\t\t}\n\t\t// Filter out ImageContent from all messages, replacing with text placeholder\n\t\treturn converted.map((msg) => {\n\t\t\tif (msg.role === \"user\" || msg.role === \"toolResult\") {\n\t\t\t\tconst content = msg.content;\n\t\t\t\tif (Array.isArray(content)) {\n\t\t\t\t\tconst hasImages = content.some((c) => c.type === \"image\");\n\t\t\t\t\tif (hasImages) {\n\t\t\t\t\t\tconst filteredContent = content\n\t\t\t\t\t\t\t.map((c) =>\n\t\t\t\t\t\t\t\tc.type === \"image\" ? { type: \"text\" as const, text: \"Image reading is disabled.\" } : c,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.filter(\n\t\t\t\t\t\t\t\t(c, i, arr) =>\n\t\t\t\t\t\t\t\t\t// Dedupe consecutive \"Image reading is disabled.\" texts\n\t\t\t\t\t\t\t\t\t!(\n\t\t\t\t\t\t\t\t\t\tc.type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\tc.text === \"Image reading is disabled.\" &&\n\t\t\t\t\t\t\t\t\t\ti > 0 &&\n\t\t\t\t\t\t\t\t\t\tarr[i - 1].type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\t(arr[i - 1] as { type: \"text\"; text: string }).text === \"Image reading is disabled.\"\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\treturn { ...msg, content: filteredContent };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn msg;\n\t\t});\n\t};\n\n\tconst extensionRunnerRef: { current?: ExtensionRunner } = {};\n\n\tagent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt: \"\",\n\t\t\tmodel,\n\t\t\tthinkingLevel,\n\t\t\ttools: [],\n\t\t},\n\t\tconvertToLlm: convertToLlmWithBlockImages,\n\t\tstreamFn: async (model, context, options) => {\n\t\t\tconst providerRetrySettings = settingsManager.getProviderRetrySettings();\n\t\t\tconst httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs();\n\t\t\t// SDKs treat timeout=0 as 0ms (immediate timeout), not \"no timeout\".\n\t\t\t// Use max int32 to effectively disable the timeout.\n\t\t\tconst effectiveTimeoutMs = httpIdleTimeoutMs === 0 ? 2147483647 : httpIdleTimeoutMs;\n\t\t\tconst timeoutMs = options?.timeoutMs ?? providerRetrySettings.timeoutMs ?? effectiveTimeoutMs;\n\t\t\tconst websocketConnectTimeoutMs =\n\t\t\t\toptions?.websocketConnectTimeoutMs ?? settingsManager.getWebSocketConnectTimeoutMs();\n\t\t\tconst headerRunner = extensionRunnerRef.current;\n\t\t\treturn modelRuntime.streamSimple(model, context, {\n\t\t\t\t...options,\n\t\t\t\ttimeoutMs,\n\t\t\t\twebsocketConnectTimeoutMs,\n\t\t\t\tmaxRetries: options?.maxRetries ?? providerRetrySettings.maxRetries,\n\t\t\t\tmaxRetryDelayMs: options?.maxRetryDelayMs ?? providerRetrySettings.maxRetryDelayMs,\n\t\t\t\ttransformHeaders: async (requestHeaders) => {\n\t\t\t\t\tconst headers = mergeProviderAttributionHeaders(\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tsettingsManager,\n\t\t\t\t\t\toptions?.sessionId,\n\t\t\t\t\t\trequestHeaders,\n\t\t\t\t\t);\n\t\t\t\t\treturn headerRunner?.hasHandlers(\"before_provider_headers\")\n\t\t\t\t\t\t? headerRunner.emitBeforeProviderHeaders(headers ?? {})\n\t\t\t\t\t\t: (headers ?? {});\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t\tonPayload: async (payload, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"before_provider_request\")) {\n\t\t\t\treturn payload;\n\t\t\t}\n\t\t\treturn runner.emitBeforeProviderRequest(payload);\n\t\t},\n\t\tonResponse: async (response, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"after_provider_response\")) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait runner.emit({\n\t\t\t\ttype: \"after_provider_response\",\n\t\t\t\tstatus: response.status,\n\t\t\t\theaders: response.headers,\n\t\t\t});\n\t\t},\n\t\tsessionId: sessionManager.getSessionId(),\n\t\ttransformContext: async (messages) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner) return messages;\n\t\t\treturn runner.emitContext(messages);\n\t\t},\n\t\tsteeringMode: settingsManager.getSteeringMode(),\n\t\tfollowUpMode: settingsManager.getFollowUpMode(),\n\t\ttransport: settingsManager.getTransport(),\n\t\tthinkingBudgets: settingsManager.getThinkingBudgets(),\n\t\tmaxRetryDelayMs: settingsManager.getProviderRetrySettings().maxRetryDelayMs,\n\t});\n\n\t// Restore messages if session has existing data\n\tif (hasExistingSession) {\n\t\tagent.state.messages = existingSession.messages;\n\t\tif (!hasThinkingEntry) {\n\t\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t\t}\n\t} else {\n\t\t// Save initial model and thinking level for new sessions so they can be restored on resume\n\t\tif (model) {\n\t\t\tsessionManager.appendModelChange(model.provider, model.id);\n\t\t}\n\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t}\n\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager,\n\t\tcwd,\n\t\tscopedModels: options.scopedModels,\n\t\tresourceLoader,\n\t\tcustomTools: options.customTools,\n\t\tmodelRuntime,\n\t\tinitialActiveToolNames,\n\t\tallowedToolNames,\n\t\texcludedToolNames,\n\t\textensionRunnerRef,\n\t\tsessionStartEvent: options.sessionStartEvent,\n\t});\n\tconst extensionsResult = resourceLoader.getExtensions();\n\n\treturn {\n\t\tsession,\n\t\textensionsResult,\n\t\tmodelFallbackMessage,\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sdk.js","sourceRoot":"","sources":["../../src/core/sdk.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from \"node:path\";\nimport { Agent, type AgentMessage, setDefaultStreamFn, type ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport { clampThinkingLevel, type Message, type Model, streamSimple } from \"@earendil-works/pi-ai/compat\";\nimport { getAgentDir } from \"../config.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { AgentSession } from \"./agent-session.ts\";\nimport { formatNoModelsAvailableMessage } from \"./auth-guidance.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport type { ExtensionRunner, LoadExtensionsResult, SessionStartEvent, ToolDefinition } from \"./extensions/index.ts\";\nimport { convertToLlm } from \"./messages.ts\";\nimport { findInitialModel } from \"./model-resolver.ts\";\nimport { ModelRuntime } from \"./model-runtime.ts\";\nimport { mergeProviderAttributionHeaders } from \"./provider-attribution.ts\";\nimport type { ResourceLoader } from \"./resource-loader.ts\";\nimport { DefaultResourceLoader } from \"./resource-loader.ts\";\nimport { getDefaultSessionDir, SessionManager } from \"./session-manager.ts\";\nimport { SettingsManager } from \"./settings-manager.ts\";\nimport { time } from \"./timings.ts\";\nimport {\n\tcreateBashTool,\n\tcreateCodingTools,\n\tcreateEditTool,\n\tcreateFindTool,\n\tcreateGrepTool,\n\tcreateLsTool,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateWriteTool,\n\ttype ToolName,\n\twithFileMutationQueue,\n} from \"./tools/index.ts\";\n\n// Preserve the pre-0.81 fallback for extensions that construct Agent instances\n// or invoke low-level agent loops without supplying streamFn. Agent core remains\n// provider-agnostic and does not import pi-ai/compat itself.\nsetDefaultStreamFn(streamSimple);\n\nexport interface CreateAgentSessionOptions {\n\t/** Working directory for project-local discovery. Default: process.cwd() */\n\tcwd?: string;\n\t/** Global config directory. Default: ~/.pi/agent */\n\tagentDir?: string;\n\n\t/** Canonical model/auth runtime. Defaults to a runtime using agentDir/auth.json and models.json. */\n\tmodelRuntime?: ModelRuntime;\n\n\t/** Model to use. Default: exact saved setting; otherwise entitled gitnexus/auto when available. */\n\tmodel?: Model<any>;\n\t/** Thinking level. Default: from settings, else 'medium' (clamped to model capabilities) */\n\tthinkingLevel?: ThinkingLevel;\n\t/** Models available for cycling (Ctrl+P in interactive mode) */\n\tscopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;\n\n\t/**\n\t * Optional default tool suppression mode when no explicit allowlist is provided.\n\t *\n\t * - \"all\": start with no tools enabled\n\t * - \"builtin\": disable the default built-in tools (read, bash, edit, write)\n\t * but keep extension/custom tools enabled\n\t */\n\tnoTools?: \"all\" | \"builtin\";\n\t/**\n\t * Optional allowlist of tool names.\n\t *\n\t * When omitted, pi enables the default built-in tools (read, bash, edit, write)\n\t * and leaves extension/custom tools enabled unless `noTools` changes that default.\n\t * When provided, only the listed tool names are enabled.\n\t */\n\ttools?: string[];\n\t/** Optional denylist of tool names to disable. Applies after `tools` when both are provided. */\n\texcludeTools?: string[];\n\t/** Custom tools to register (in addition to built-in tools). */\n\tcustomTools?: ToolDefinition[];\n\n\t/** Resource loader. When omitted, DefaultResourceLoader is used. */\n\tresourceLoader?: ResourceLoader;\n\n\t/** Session manager. Default: SessionManager.create(cwd) */\n\tsessionManager?: SessionManager;\n\n\t/** Settings manager. Default: SettingsManager.create(cwd, agentDir) */\n\tsettingsManager?: SettingsManager;\n\t/** Session start event metadata for extension runtime startup. */\n\tsessionStartEvent?: SessionStartEvent;\n}\n\n/** Result from createAgentSession */\nexport interface CreateAgentSessionResult {\n\t/** The created session */\n\tsession: AgentSession;\n\t/** Extensions result (for UI context setup in interactive mode) */\n\textensionsResult: LoadExtensionsResult;\n\t/** Warning if session was restored with a different model than saved */\n\tmodelFallbackMessage?: string;\n}\n\n// Re-exports\n\nexport * from \"./agent-session-runtime.ts\";\nexport type {\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionContext,\n\tExtensionFactory,\n\tInlineExtension,\n\tSlashCommandInfo,\n\tSlashCommandSource,\n\tToolDefinition,\n} from \"./extensions/index.ts\";\nexport type { PromptTemplate } from \"./prompt-templates.ts\";\nexport type { Skill } from \"./skills.ts\";\nexport type { Tool } from \"./tools/index.ts\";\n\nexport {\n\twithFileMutationQueue,\n\t// Tool factories (for custom cwd)\n\tcreateCodingTools,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateBashTool,\n\tcreateEditTool,\n\tcreateWriteTool,\n\tcreateGrepTool,\n\tcreateFindTool,\n\tcreateLsTool,\n};\n\n// Helper Functions\n\nfunction getDefaultAgentDir(): string {\n\treturn getAgentDir();\n}\n\n/**\n * Create an AgentSession with the specified options.\n *\n * @example\n * ```typescript\n * // Minimal - uses defaults\n * const { session } = await createAgentSession();\n *\n * // With explicit model\n * import { getModel } from '@earendil-works/pi-ai';\n * const { session } = await createAgentSession({\n * model: getModel('anthropic', 'claude-opus-4-5'),\n * thinkingLevel: 'high',\n * });\n *\n * // Continue previous session\n * const { session, modelFallbackMessage } = await createAgentSession({\n * continueSession: true,\n * });\n *\n * // Full control\n * const loader = new DefaultResourceLoader({\n * cwd: process.cwd(),\n * agentDir: getAgentDir(),\n * settingsManager: SettingsManager.create(),\n * });\n * await loader.reload();\n * const { session } = await createAgentSession({\n * model: myModel,\n * tools: [\"read\", \"bash\"],\n * resourceLoader: loader,\n * sessionManager: SessionManager.inMemory(),\n * });\n * ```\n */\nexport async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {\n\tconst cwd = resolvePath(options.cwd ?? options.sessionManager?.getCwd() ?? process.cwd());\n\tconst agentDir = options.agentDir ? resolvePath(options.agentDir) : getDefaultAgentDir();\n\tlet resourceLoader = options.resourceLoader;\n\n\tconst authPath = options.agentDir ? join(agentDir, \"auth.json\") : undefined;\n\tconst modelsPath = options.agentDir ? join(agentDir, \"models.json\") : undefined;\n\tconst modelRuntime = options.modelRuntime ?? (await ModelRuntime.create({ authPath, modelsPath }));\n\n\tconst settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);\n\tconst sessionManager = options.sessionManager ?? SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir));\n\n\tif (!resourceLoader) {\n\t\tresourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager });\n\t\tawait resourceLoader.reload();\n\t\ttime(\"resourceLoader.reload\");\n\t}\n\n\t// Check if session has existing data to restore\n\tconst existingSession = sessionManager.buildSessionContext();\n\tconst hasExistingSession = existingSession.messages.length > 0;\n\tconst hasThinkingEntry = sessionManager.getBranch().some((entry) => entry.type === \"thinking_level_change\");\n\n\tlet model = options.model;\n\tlet modelFallbackMessage: string | undefined;\n\n\t// If session has data, try to restore model from it\n\tif (!model && hasExistingSession && existingSession.model) {\n\t\tconst restoredModel = modelRuntime.getModel(existingSession.model.provider, existingSession.model.modelId);\n\t\tif (restoredModel) {\n\t\t\tmodel = restoredModel;\n\t\t}\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = `Could not restore model ${existingSession.model.provider}/${existingSession.model.modelId}`;\n\t\t}\n\t}\n\n\t// If still no model, use the exact saved default or entitled hosted Auto.\n\tif (!model) {\n\t\tconst result = await findInitialModel({\n\t\t\tscopedModels: [],\n\t\t\tisContinuing: hasExistingSession,\n\t\t\tdefaultProvider: settingsManager.getDefaultProvider(),\n\t\t\tdefaultModelId: settingsManager.getDefaultModel(),\n\t\t\tdefaultThinkingLevel: settingsManager.getDefaultThinkingLevel(),\n\t\t\tmodelRuntime,\n\t\t});\n\t\tmodel = result.model;\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = formatNoModelsAvailableMessage();\n\t\t} else if (modelFallbackMessage) {\n\t\t\tmodelFallbackMessage += `. Using ${model.provider}/${model.id}`;\n\t\t}\n\t}\n\n\tlet thinkingLevel = options.thinkingLevel;\n\n\t// If session has data, restore thinking level from it\n\tif (thinkingLevel === undefined && hasExistingSession) {\n\t\tthinkingLevel = hasThinkingEntry\n\t\t\t? (existingSession.thinkingLevel as ThinkingLevel)\n\t\t\t: (settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL);\n\t}\n\n\t// Fall back to settings default\n\tif (thinkingLevel === undefined) {\n\t\tthinkingLevel = settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;\n\t}\n\n\t// Clamp to model capabilities\n\tif (!model) {\n\t\tthinkingLevel = \"off\";\n\t} else {\n\t\tthinkingLevel = clampThinkingLevel(model, thinkingLevel) as ThinkingLevel;\n\t}\n\n\tconst defaultActiveToolNames: ToolName[] = [\"read\", \"bash\", \"edit\", \"write\", \"websearch\"];\n\tconst allowedToolNames = options.tools ?? (options.noTools === \"all\" ? [] : undefined);\n\tconst excludedToolNames = options.excludeTools;\n\tconst excludedToolNameSet = excludedToolNames ? new Set(excludedToolNames) : undefined;\n\tconst initialActiveToolNames: string[] = (\n\t\toptions.tools ? [...options.tools] : options.noTools ? [] : defaultActiveToolNames\n\t).filter((name) => !excludedToolNameSet?.has(name));\n\n\tlet agent: Agent;\n\n\t// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)\n\tconst convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {\n\t\tconst converted = convertToLlm(messages);\n\t\t// Check setting dynamically so mid-session changes take effect\n\t\tif (!settingsManager.getBlockImages()) {\n\t\t\treturn converted;\n\t\t}\n\t\t// Filter out ImageContent from all messages, replacing with text placeholder\n\t\treturn converted.map((msg) => {\n\t\t\tif (msg.role === \"user\" || msg.role === \"toolResult\") {\n\t\t\t\tconst content = msg.content;\n\t\t\t\tif (Array.isArray(content)) {\n\t\t\t\t\tconst hasImages = content.some((c) => c.type === \"image\");\n\t\t\t\t\tif (hasImages) {\n\t\t\t\t\t\tconst filteredContent = content\n\t\t\t\t\t\t\t.map((c) =>\n\t\t\t\t\t\t\t\tc.type === \"image\" ? { type: \"text\" as const, text: \"Image reading is disabled.\" } : c,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.filter(\n\t\t\t\t\t\t\t\t(c, i, arr) =>\n\t\t\t\t\t\t\t\t\t// Dedupe consecutive \"Image reading is disabled.\" texts\n\t\t\t\t\t\t\t\t\t!(\n\t\t\t\t\t\t\t\t\t\tc.type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\tc.text === \"Image reading is disabled.\" &&\n\t\t\t\t\t\t\t\t\t\ti > 0 &&\n\t\t\t\t\t\t\t\t\t\tarr[i - 1].type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\t(arr[i - 1] as { type: \"text\"; text: string }).text === \"Image reading is disabled.\"\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\treturn { ...msg, content: filteredContent };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn msg;\n\t\t});\n\t};\n\n\tconst extensionRunnerRef: { current?: ExtensionRunner } = {};\n\n\tagent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt: \"\",\n\t\t\tmodel,\n\t\t\tthinkingLevel,\n\t\t\ttools: [],\n\t\t},\n\t\tconvertToLlm: convertToLlmWithBlockImages,\n\t\tstreamFn: async (model, context, options) => {\n\t\t\tconst providerRetrySettings = settingsManager.getProviderRetrySettings();\n\t\t\tconst httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs();\n\t\t\t// SDKs treat timeout=0 as 0ms (immediate timeout), not \"no timeout\".\n\t\t\t// Use max int32 to effectively disable the timeout.\n\t\t\tconst effectiveTimeoutMs = httpIdleTimeoutMs === 0 ? 2147483647 : httpIdleTimeoutMs;\n\t\t\tconst timeoutMs = options?.timeoutMs ?? providerRetrySettings.timeoutMs ?? effectiveTimeoutMs;\n\t\t\tconst websocketConnectTimeoutMs =\n\t\t\t\toptions?.websocketConnectTimeoutMs ?? settingsManager.getWebSocketConnectTimeoutMs();\n\t\t\tconst headerRunner = extensionRunnerRef.current;\n\t\t\treturn modelRuntime.streamSimple(model, context, {\n\t\t\t\t...options,\n\t\t\t\ttimeoutMs,\n\t\t\t\twebsocketConnectTimeoutMs,\n\t\t\t\tmaxRetries: options?.maxRetries ?? providerRetrySettings.maxRetries,\n\t\t\t\tmaxRetryDelayMs: options?.maxRetryDelayMs ?? providerRetrySettings.maxRetryDelayMs,\n\t\t\t\ttransformHeaders: async (requestHeaders) => {\n\t\t\t\t\tconst headers = mergeProviderAttributionHeaders(\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tsettingsManager,\n\t\t\t\t\t\toptions?.sessionId,\n\t\t\t\t\t\trequestHeaders,\n\t\t\t\t\t);\n\t\t\t\t\treturn headerRunner?.hasHandlers(\"before_provider_headers\")\n\t\t\t\t\t\t? headerRunner.emitBeforeProviderHeaders(headers ?? {})\n\t\t\t\t\t\t: (headers ?? {});\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t\tonPayload: async (payload, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"before_provider_request\")) {\n\t\t\t\treturn payload;\n\t\t\t}\n\t\t\treturn runner.emitBeforeProviderRequest(payload);\n\t\t},\n\t\tonResponse: async (response, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"after_provider_response\")) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait runner.emit({\n\t\t\t\ttype: \"after_provider_response\",\n\t\t\t\tstatus: response.status,\n\t\t\t\theaders: response.headers,\n\t\t\t});\n\t\t},\n\t\tsessionId: sessionManager.getSessionId(),\n\t\ttransformContext: async (messages) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner) return messages;\n\t\t\treturn runner.emitContext(messages);\n\t\t},\n\t\tsteeringMode: settingsManager.getSteeringMode(),\n\t\tfollowUpMode: settingsManager.getFollowUpMode(),\n\t\ttransport: settingsManager.getTransport(),\n\t\tthinkingBudgets: settingsManager.getThinkingBudgets(),\n\t\tmaxRetryDelayMs: settingsManager.getProviderRetrySettings().maxRetryDelayMs,\n\t});\n\n\t// Restore messages if session has existing data\n\tif (hasExistingSession) {\n\t\tagent.state.messages = existingSession.messages;\n\t\tif (!hasThinkingEntry) {\n\t\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t\t}\n\t} else {\n\t\t// Save initial model and thinking level for new sessions so they can be restored on resume\n\t\tif (model) {\n\t\t\tsessionManager.appendModelChange(model.provider, model.id);\n\t\t}\n\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t}\n\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager,\n\t\tcwd,\n\t\tscopedModels: options.scopedModels,\n\t\tresourceLoader,\n\t\tcustomTools: options.customTools,\n\t\tmodelRuntime,\n\t\tinitialActiveToolNames,\n\t\tallowedToolNames,\n\t\texcludedToolNames,\n\t\textensionRunnerRef,\n\t\tsessionStartEvent: options.sessionStartEvent,\n\t});\n\tconst extensionsResult = resourceLoader.getExtensions();\n\n\treturn {\n\t\tsession,\n\t\textensionsResult,\n\t\tmodelFallbackMessage,\n\t};\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websearch.d.ts","sourceRoot":"","sources":["../../../src/core/tools/websearch.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,wBAAwB,CAAC;AAsBtF,QAAA,MAAM,eAAe;;;EAKnB,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC;AAEhE,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,aAAa,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACpC,oDAAoD;IACpD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAOD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAcrD;AAED,iEAAiE;AACjE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAoB1D;AAED,wBAAgB,6BAA6B,CAC5C,OAAO,CAAC,EAAE,oBAAoB,GAC5B,cAAc,CAAC,OAAO,eAAe,EAAE,oBAAoB,GAAG,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"websearch.d.ts","sourceRoot":"","sources":["../../../src/core/tools/websearch.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,wBAAwB,CAAC;AAsBtF,QAAA,MAAM,eAAe;;;EAKnB,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC;AAEhE,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,aAAa,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACpC,oDAAoD;IACpD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAOD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAcrD;AAED,iEAAiE;AACjE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAoB1D;AAED,wBAAgB,6BAA6B,CAC5C,OAAO,CAAC,EAAE,oBAAoB,GAC5B,cAAc,CAAC,OAAO,eAAe,EAAE,oBAAoB,GAAG,SAAS,CAAC,CA4F1E;AAED,eAAO,MAAM,mBAAmB,0IAC2B,CAAC","sourcesContent":["import { Text } from \"@earendil-works/pi-tui\";\nimport { type Static, Type } from \"typebox\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { getTextOutput, str } from \"./render-utils.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\n\n/**\n * Web search through Exa's hosted MCP endpoint.\n *\n * The endpoint answers unauthenticated requests on a free plan, so search works\n * with no configuration at all. EXA_API_KEY lifts the rate limit and is what a\n * deployment should use — Exa's own guidance is to authenticate for sustained\n * use, and the anonymous pool is shared with every other unauthenticated caller\n * on the internet.\n *\n * Only the transport is spoken here rather than pulling in an MCP client: this\n * is one JSON-RPC method against one known endpoint, and the response is a\n * text block either way.\n */\n\nconst EXA_MCP_URL = \"https://mcp.exa.ai/mcp\";\nconst DEFAULT_RESULTS = 8;\nconst REQUEST_TIMEOUT_MS = 25_000;\n\nconst websearchSchema = Type.Object({\n\tquery: Type.String({ description: \"What to search the web for\" }),\n\tnumResults: Type.Optional(\n\t\tType.Number({ description: `Maximum number of results to return (default: ${DEFAULT_RESULTS})` }),\n\t),\n});\n\nexport type WebSearchToolInput = Static<typeof websearchSchema>;\n\nexport interface WebSearchToolDetails {\n\tquery: string;\n\t/** True when a key was used, so the UI can explain a rate limit accurately. */\n\tauthenticated: boolean;\n}\n\nexport interface WebSearchToolOptions {\n\t/** Overridable so tests never reach the network. */\n\tfetchImpl?: typeof fetch;\n\tapiKey?: string | undefined;\n}\n\n/** Exa reads the key from a query parameter on the hosted endpoint. */\nfunction endpointFor(apiKey: string | undefined): string {\n\treturn apiKey ? `${EXA_MCP_URL}?exaApiKey=${encodeURIComponent(apiKey)}` : EXA_MCP_URL;\n}\n\n/**\n * The endpoint answers `application/json` or an SSE stream depending on how it\n * feels; both carry the same JSON-RPC envelope, so pull the payload out of\n * whichever arrived.\n */\nexport function parseMcpPayload(body: string): unknown {\n\tconst trimmed = body.trim();\n\tif (!trimmed) throw new Error(\"Search returned an empty response\");\n\n\tif (trimmed.startsWith(\"{\")) return JSON.parse(trimmed);\n\n\t// SSE: one or more `data:` lines, last one wins.\n\tconst dataLines = trimmed\n\t\t.split(\"\\n\")\n\t\t.filter((line) => line.startsWith(\"data:\"))\n\t\t.map((line) => line.slice(5).trim());\n\tconst last = dataLines.at(-1);\n\tif (!last) throw new Error(\"Search returned an unrecognized response\");\n\treturn JSON.parse(last);\n}\n\n/** Flatten the MCP tool result into the text the model reads. */\nexport function extractResultText(payload: unknown): string {\n\tconst envelope = payload as {\n\t\terror?: { message?: string };\n\t\tresult?: { content?: Array<{ type?: string; text?: string }>; isError?: boolean };\n\t};\n\n\tif (envelope?.error?.message) throw new Error(envelope.error.message);\n\n\tconst content = envelope?.result?.content;\n\tif (!Array.isArray(content)) throw new Error(\"Search returned no results\");\n\n\tconst text = content\n\t\t.filter((part) => part?.type === \"text\" && typeof part.text === \"string\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\\n\")\n\t\t.trim();\n\n\tif (!text) throw new Error(\"Search returned no results\");\n\tif (envelope.result?.isError) throw new Error(text);\n\treturn text;\n}\n\nexport function createWebSearchToolDefinition(\n\toptions?: WebSearchToolOptions,\n): ToolDefinition<typeof websearchSchema, WebSearchToolDetails | undefined> {\n\treturn {\n\t\tname: \"websearch\",\n\t\tlabel: \"websearch\",\n\t\tdescription:\n\t\t\t\"Search the web and return ranked results with excerpts. Use for current information, documentation, error messages, and anything outside the repository.\",\n\t\tpromptSnippet: \"Search the web for current information\",\n\t\tpromptGuidelines: [\"Use websearch for anything outside the repository; do not guess at current facts.\"],\n\t\tparameters: websearchSchema,\n\t\tasync execute(_toolCallId, { query, numResults }, signal?: AbortSignal) {\n\t\t\tconst doFetch = options?.fetchImpl ?? fetch;\n\t\t\tconst apiKey = options?.apiKey ?? process.env.EXA_API_KEY;\n\n\t\t\t// Bound the request ourselves and honour an outer abort, so a hung\n\t\t\t// endpoint cannot wedge the turn.\n\t\t\tconst controller = new AbortController();\n\t\t\tconst timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n\t\t\tconst onAbort = () => controller.abort();\n\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\ttry {\n\t\t\t\tlet response: Response;\n\t\t\t\ttry {\n\t\t\t\t\tresponse = await doFetch(endpointFor(apiKey), {\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\theaders: { \"content-type\": \"application/json\", accept: \"application/json, text/event-stream\" },\n\t\t\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\t\t\tjsonrpc: \"2.0\",\n\t\t\t\t\t\t\tid: 1,\n\t\t\t\t\t\t\tmethod: \"tools/call\",\n\t\t\t\t\t\t\tparams: {\n\t\t\t\t\t\t\t\tname: \"web_search_exa\",\n\t\t\t\t\t\t\t\targuments: { query, numResults: numResults ?? DEFAULT_RESULTS, type: \"auto\" },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\t\t\tif (controller.signal.aborted) {\n\t\t\t\t\t\tthrow new Error(`Search timed out after ${REQUEST_TIMEOUT_MS / 1000}s`);\n\t\t\t\t\t}\n\t\t\t\t\tthrow new Error(`Search request failed: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\t\t}\n\n\t\t\t\t// A rate limit is the one failure worth naming: it is expected on the\n\t\t\t\t// free plan, and the fix is specific rather than \"try again\".\n\t\t\t\tif (response.status === 429) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\tapiKey\n\t\t\t\t\t\t\t? \"Search rate limit reached for this Exa API key.\"\n\t\t\t\t\t\t\t: \"Search rate limit reached on Exa's free plan. Set EXA_API_KEY to raise it.\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthrow new Error(`Search failed with HTTP ${response.status}`);\n\t\t\t\t}\n\n\t\t\t\tlet body: string;\n\t\t\t\ttry {\n\t\t\t\t\tbody = await response.text();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\t\t\tif (controller.signal.aborted) {\n\t\t\t\t\t\tthrow new Error(`Search timed out after ${REQUEST_TIMEOUT_MS / 1000}s`);\n\t\t\t\t\t}\n\t\t\t\t\tthrow new Error(`Search response failed: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\t\t}\n\n\t\t\t\tconst text = extractResultText(parseMcpPayload(body));\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text }],\n\t\t\t\t\tdetails: { query, authenticated: apiKey !== undefined },\n\t\t\t\t};\n\t\t\t} finally {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t}\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\tconst query = str(args?.query);\n\t\t\ttext.setText(query ? theme.fg(\"muted\", query) : \"\");\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, _options: ToolRenderResultOptions, theme: Theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\t// Results are text only; images never appear in a search payload.\n\t\t\ttext.setText(theme.fg(\"muted\", getTextOutput(result as never, false)));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport const createWebSearchTool = (options?: WebSearchToolOptions) =>\n\twrapToolDefinition(createWebSearchToolDefinition(options));\n"]}
|
|
@@ -83,49 +83,64 @@ export function createWebSearchToolDefinition(options) {
|
|
|
83
83
|
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
84
84
|
const onAbort = () => controller.abort();
|
|
85
85
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
86
|
-
let response;
|
|
87
86
|
try {
|
|
88
|
-
response
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
87
|
+
let response;
|
|
88
|
+
try {
|
|
89
|
+
response = await doFetch(endpointFor(apiKey), {
|
|
90
|
+
method: "POST",
|
|
91
|
+
headers: { "content-type": "application/json", accept: "application/json, text/event-stream" },
|
|
92
|
+
body: JSON.stringify({
|
|
93
|
+
jsonrpc: "2.0",
|
|
94
|
+
id: 1,
|
|
95
|
+
method: "tools/call",
|
|
96
|
+
params: {
|
|
97
|
+
name: "web_search_exa",
|
|
98
|
+
arguments: { query, numResults: numResults ?? DEFAULT_RESULTS, type: "auto" },
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
signal: controller.signal,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (signal?.aborted)
|
|
106
|
+
throw new Error("Operation aborted");
|
|
107
|
+
if (controller.signal.aborted) {
|
|
108
|
+
throw new Error(`Search timed out after ${REQUEST_TIMEOUT_MS / 1000}s`);
|
|
109
|
+
}
|
|
110
|
+
throw new Error(`Search request failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
111
|
+
}
|
|
112
|
+
// A rate limit is the one failure worth naming: it is expected on the
|
|
113
|
+
// free plan, and the fix is specific rather than "try again".
|
|
114
|
+
if (response.status === 429) {
|
|
115
|
+
throw new Error(apiKey
|
|
116
|
+
? "Search rate limit reached for this Exa API key."
|
|
117
|
+
: "Search rate limit reached on Exa's free plan. Set EXA_API_KEY to raise it.");
|
|
118
|
+
}
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
throw new Error(`Search failed with HTTP ${response.status}`);
|
|
121
|
+
}
|
|
122
|
+
let body;
|
|
123
|
+
try {
|
|
124
|
+
body = await response.text();
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
if (signal?.aborted)
|
|
128
|
+
throw new Error("Operation aborted");
|
|
129
|
+
if (controller.signal.aborted) {
|
|
130
|
+
throw new Error(`Search timed out after ${REQUEST_TIMEOUT_MS / 1000}s`);
|
|
131
|
+
}
|
|
132
|
+
throw new Error(`Search response failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
133
|
+
}
|
|
134
|
+
const text = extractResultText(parseMcpPayload(body));
|
|
135
|
+
return {
|
|
136
|
+
content: [{ type: "text", text }],
|
|
137
|
+
details: { query, authenticated: apiKey !== undefined },
|
|
138
|
+
};
|
|
109
139
|
}
|
|
110
140
|
finally {
|
|
111
141
|
clearTimeout(timer);
|
|
112
142
|
signal?.removeEventListener("abort", onAbort);
|
|
113
143
|
}
|
|
114
|
-
// A rate limit is the one failure worth naming: it is expected on the
|
|
115
|
-
// free plan, and the fix is specific rather than "try again".
|
|
116
|
-
if (response.status === 429) {
|
|
117
|
-
throw new Error(apiKey
|
|
118
|
-
? "Search rate limit reached for this Exa API key."
|
|
119
|
-
: "Search rate limit reached on Exa's free plan. Set EXA_API_KEY to raise it.");
|
|
120
|
-
}
|
|
121
|
-
if (!response.ok) {
|
|
122
|
-
throw new Error(`Search failed with HTTP ${response.status}`);
|
|
123
|
-
}
|
|
124
|
-
const text = extractResultText(parseMcpPayload(await response.text()));
|
|
125
|
-
return {
|
|
126
|
-
content: [{ type: "text", text }],
|
|
127
|
-
details: { query, authenticated: apiKey !== undefined },
|
|
128
|
-
};
|
|
129
144
|
},
|
|
130
145
|
renderCall(args, theme, context) {
|
|
131
146
|
const text = context.lastComponent ?? new Text("", 0, 0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websearch.js","sourceRoot":"","sources":["../../../src/core/tools/websearch.ts"],"names":[],"mappings":"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","sourcesContent":["import { Text } from \"@earendil-works/pi-tui\";\nimport { type Static, Type } from \"typebox\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { getTextOutput, str } from \"./render-utils.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\n\n/**\n * Web search through Exa's hosted MCP endpoint.\n *\n * The endpoint answers unauthenticated requests on a free plan, so search works\n * with no configuration at all. EXA_API_KEY lifts the rate limit and is what a\n * deployment should use — Exa's own guidance is to authenticate for sustained\n * use, and the anonymous pool is shared with every other unauthenticated caller\n * on the internet.\n *\n * Only the transport is spoken here rather than pulling in an MCP client: this\n * is one JSON-RPC method against one known endpoint, and the response is a\n * text block either way.\n */\n\nconst EXA_MCP_URL = \"https://mcp.exa.ai/mcp\";\nconst DEFAULT_RESULTS = 8;\nconst REQUEST_TIMEOUT_MS = 25_000;\n\nconst websearchSchema = Type.Object({\n\tquery: Type.String({ description: \"What to search the web for\" }),\n\tnumResults: Type.Optional(\n\t\tType.Number({ description: `Maximum number of results to return (default: ${DEFAULT_RESULTS})` }),\n\t),\n});\n\nexport type WebSearchToolInput = Static<typeof websearchSchema>;\n\nexport interface WebSearchToolDetails {\n\tquery: string;\n\t/** True when a key was used, so the UI can explain a rate limit accurately. */\n\tauthenticated: boolean;\n}\n\nexport interface WebSearchToolOptions {\n\t/** Overridable so tests never reach the network. */\n\tfetchImpl?: typeof fetch;\n\tapiKey?: string | undefined;\n}\n\n/** Exa reads the key from a query parameter on the hosted endpoint. */\nfunction endpointFor(apiKey: string | undefined): string {\n\treturn apiKey ? `${EXA_MCP_URL}?exaApiKey=${encodeURIComponent(apiKey)}` : EXA_MCP_URL;\n}\n\n/**\n * The endpoint answers `application/json` or an SSE stream depending on how it\n * feels; both carry the same JSON-RPC envelope, so pull the payload out of\n * whichever arrived.\n */\nexport function parseMcpPayload(body: string): unknown {\n\tconst trimmed = body.trim();\n\tif (!trimmed) throw new Error(\"Search returned an empty response\");\n\n\tif (trimmed.startsWith(\"{\")) return JSON.parse(trimmed);\n\n\t// SSE: one or more `data:` lines, last one wins.\n\tconst dataLines = trimmed\n\t\t.split(\"\\n\")\n\t\t.filter((line) => line.startsWith(\"data:\"))\n\t\t.map((line) => line.slice(5).trim());\n\tconst last = dataLines.at(-1);\n\tif (!last) throw new Error(\"Search returned an unrecognized response\");\n\treturn JSON.parse(last);\n}\n\n/** Flatten the MCP tool result into the text the model reads. */\nexport function extractResultText(payload: unknown): string {\n\tconst envelope = payload as {\n\t\terror?: { message?: string };\n\t\tresult?: { content?: Array<{ type?: string; text?: string }>; isError?: boolean };\n\t};\n\n\tif (envelope?.error?.message) throw new Error(envelope.error.message);\n\n\tconst content = envelope?.result?.content;\n\tif (!Array.isArray(content)) throw new Error(\"Search returned no results\");\n\n\tconst text = content\n\t\t.filter((part) => part?.type === \"text\" && typeof part.text === \"string\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\\n\")\n\t\t.trim();\n\n\tif (!text) throw new Error(\"Search returned no results\");\n\tif (envelope.result?.isError) throw new Error(text);\n\treturn text;\n}\n\nexport function createWebSearchToolDefinition(\n\toptions?: WebSearchToolOptions,\n): ToolDefinition<typeof websearchSchema, WebSearchToolDetails | undefined> {\n\treturn {\n\t\tname: \"websearch\",\n\t\tlabel: \"websearch\",\n\t\tdescription:\n\t\t\t\"Search the web and return ranked results with excerpts. Use for current information, documentation, error messages, and anything outside the repository.\",\n\t\tpromptSnippet: \"Search the web for current information\",\n\t\tpromptGuidelines: [\"Use websearch for anything outside the repository; do not guess at current facts.\"],\n\t\tparameters: websearchSchema,\n\t\tasync execute(_toolCallId, { query, numResults }, signal?: AbortSignal) {\n\t\t\tconst doFetch = options?.fetchImpl ?? fetch;\n\t\t\tconst apiKey = options?.apiKey ?? process.env.EXA_API_KEY;\n\n\t\t\t// Bound the request ourselves and honour an outer abort, so a hung\n\t\t\t// endpoint cannot wedge the turn.\n\t\t\tconst controller = new AbortController();\n\t\t\tconst timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n\t\t\tconst onAbort = () => controller.abort();\n\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\tlet response: Response;\n\t\t\ttry {\n\t\t\t\tresponse = await doFetch(endpointFor(apiKey), {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: { \"content-type\": \"application/json\", accept: \"application/json, text/event-stream\" },\n\t\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\t\tjsonrpc: \"2.0\",\n\t\t\t\t\t\tid: 1,\n\t\t\t\t\t\tmethod: \"tools/call\",\n\t\t\t\t\t\tparams: {\n\t\t\t\t\t\t\tname: \"web_search_exa\",\n\t\t\t\t\t\t\targuments: { query, numResults: numResults ?? DEFAULT_RESULTS, type: \"auto\" },\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\t\tif (controller.signal.aborted) throw new Error(`Search timed out after ${REQUEST_TIMEOUT_MS / 1000}s`);\n\t\t\t\tthrow new Error(`Search request failed: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\t} finally {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t}\n\n\t\t\t// A rate limit is the one failure worth naming: it is expected on the\n\t\t\t// free plan, and the fix is specific rather than \"try again\".\n\t\t\tif (response.status === 429) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\tapiKey\n\t\t\t\t\t\t? \"Search rate limit reached for this Exa API key.\"\n\t\t\t\t\t\t: \"Search rate limit reached on Exa's free plan. Set EXA_API_KEY to raise it.\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(`Search failed with HTTP ${response.status}`);\n\t\t\t}\n\n\t\t\tconst text = extractResultText(parseMcpPayload(await response.text()));\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text }],\n\t\t\t\tdetails: { query, authenticated: apiKey !== undefined },\n\t\t\t};\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\tconst query = str(args?.query);\n\t\t\ttext.setText(query ? theme.fg(\"muted\", query) : \"\");\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, _options: ToolRenderResultOptions, theme: Theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\t// Results are text only; images never appear in a search payload.\n\t\t\ttext.setText(theme.fg(\"muted\", getTextOutput(result as never, false)));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport const createWebSearchTool = (options?: WebSearchToolOptions) =>\n\twrapToolDefinition(createWebSearchToolDefinition(options));\n"]}
|
|
1
|
+
{"version":3,"file":"websearch.js","sourceRoot":"","sources":["../../../src/core/tools/websearch.ts"],"names":[],"mappings":"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","sourcesContent":["import { Text } from \"@earendil-works/pi-tui\";\nimport { type Static, Type } from \"typebox\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { getTextOutput, str } from \"./render-utils.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\n\n/**\n * Web search through Exa's hosted MCP endpoint.\n *\n * The endpoint answers unauthenticated requests on a free plan, so search works\n * with no configuration at all. EXA_API_KEY lifts the rate limit and is what a\n * deployment should use — Exa's own guidance is to authenticate for sustained\n * use, and the anonymous pool is shared with every other unauthenticated caller\n * on the internet.\n *\n * Only the transport is spoken here rather than pulling in an MCP client: this\n * is one JSON-RPC method against one known endpoint, and the response is a\n * text block either way.\n */\n\nconst EXA_MCP_URL = \"https://mcp.exa.ai/mcp\";\nconst DEFAULT_RESULTS = 8;\nconst REQUEST_TIMEOUT_MS = 25_000;\n\nconst websearchSchema = Type.Object({\n\tquery: Type.String({ description: \"What to search the web for\" }),\n\tnumResults: Type.Optional(\n\t\tType.Number({ description: `Maximum number of results to return (default: ${DEFAULT_RESULTS})` }),\n\t),\n});\n\nexport type WebSearchToolInput = Static<typeof websearchSchema>;\n\nexport interface WebSearchToolDetails {\n\tquery: string;\n\t/** True when a key was used, so the UI can explain a rate limit accurately. */\n\tauthenticated: boolean;\n}\n\nexport interface WebSearchToolOptions {\n\t/** Overridable so tests never reach the network. */\n\tfetchImpl?: typeof fetch;\n\tapiKey?: string | undefined;\n}\n\n/** Exa reads the key from a query parameter on the hosted endpoint. */\nfunction endpointFor(apiKey: string | undefined): string {\n\treturn apiKey ? `${EXA_MCP_URL}?exaApiKey=${encodeURIComponent(apiKey)}` : EXA_MCP_URL;\n}\n\n/**\n * The endpoint answers `application/json` or an SSE stream depending on how it\n * feels; both carry the same JSON-RPC envelope, so pull the payload out of\n * whichever arrived.\n */\nexport function parseMcpPayload(body: string): unknown {\n\tconst trimmed = body.trim();\n\tif (!trimmed) throw new Error(\"Search returned an empty response\");\n\n\tif (trimmed.startsWith(\"{\")) return JSON.parse(trimmed);\n\n\t// SSE: one or more `data:` lines, last one wins.\n\tconst dataLines = trimmed\n\t\t.split(\"\\n\")\n\t\t.filter((line) => line.startsWith(\"data:\"))\n\t\t.map((line) => line.slice(5).trim());\n\tconst last = dataLines.at(-1);\n\tif (!last) throw new Error(\"Search returned an unrecognized response\");\n\treturn JSON.parse(last);\n}\n\n/** Flatten the MCP tool result into the text the model reads. */\nexport function extractResultText(payload: unknown): string {\n\tconst envelope = payload as {\n\t\terror?: { message?: string };\n\t\tresult?: { content?: Array<{ type?: string; text?: string }>; isError?: boolean };\n\t};\n\n\tif (envelope?.error?.message) throw new Error(envelope.error.message);\n\n\tconst content = envelope?.result?.content;\n\tif (!Array.isArray(content)) throw new Error(\"Search returned no results\");\n\n\tconst text = content\n\t\t.filter((part) => part?.type === \"text\" && typeof part.text === \"string\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\\n\")\n\t\t.trim();\n\n\tif (!text) throw new Error(\"Search returned no results\");\n\tif (envelope.result?.isError) throw new Error(text);\n\treturn text;\n}\n\nexport function createWebSearchToolDefinition(\n\toptions?: WebSearchToolOptions,\n): ToolDefinition<typeof websearchSchema, WebSearchToolDetails | undefined> {\n\treturn {\n\t\tname: \"websearch\",\n\t\tlabel: \"websearch\",\n\t\tdescription:\n\t\t\t\"Search the web and return ranked results with excerpts. Use for current information, documentation, error messages, and anything outside the repository.\",\n\t\tpromptSnippet: \"Search the web for current information\",\n\t\tpromptGuidelines: [\"Use websearch for anything outside the repository; do not guess at current facts.\"],\n\t\tparameters: websearchSchema,\n\t\tasync execute(_toolCallId, { query, numResults }, signal?: AbortSignal) {\n\t\t\tconst doFetch = options?.fetchImpl ?? fetch;\n\t\t\tconst apiKey = options?.apiKey ?? process.env.EXA_API_KEY;\n\n\t\t\t// Bound the request ourselves and honour an outer abort, so a hung\n\t\t\t// endpoint cannot wedge the turn.\n\t\t\tconst controller = new AbortController();\n\t\t\tconst timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n\t\t\tconst onAbort = () => controller.abort();\n\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\ttry {\n\t\t\t\tlet response: Response;\n\t\t\t\ttry {\n\t\t\t\t\tresponse = await doFetch(endpointFor(apiKey), {\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\theaders: { \"content-type\": \"application/json\", accept: \"application/json, text/event-stream\" },\n\t\t\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\t\t\tjsonrpc: \"2.0\",\n\t\t\t\t\t\t\tid: 1,\n\t\t\t\t\t\t\tmethod: \"tools/call\",\n\t\t\t\t\t\t\tparams: {\n\t\t\t\t\t\t\t\tname: \"web_search_exa\",\n\t\t\t\t\t\t\t\targuments: { query, numResults: numResults ?? DEFAULT_RESULTS, type: \"auto\" },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\t\t\tif (controller.signal.aborted) {\n\t\t\t\t\t\tthrow new Error(`Search timed out after ${REQUEST_TIMEOUT_MS / 1000}s`);\n\t\t\t\t\t}\n\t\t\t\t\tthrow new Error(`Search request failed: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\t\t}\n\n\t\t\t\t// A rate limit is the one failure worth naming: it is expected on the\n\t\t\t\t// free plan, and the fix is specific rather than \"try again\".\n\t\t\t\tif (response.status === 429) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\tapiKey\n\t\t\t\t\t\t\t? \"Search rate limit reached for this Exa API key.\"\n\t\t\t\t\t\t\t: \"Search rate limit reached on Exa's free plan. Set EXA_API_KEY to raise it.\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthrow new Error(`Search failed with HTTP ${response.status}`);\n\t\t\t\t}\n\n\t\t\t\tlet body: string;\n\t\t\t\ttry {\n\t\t\t\t\tbody = await response.text();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\t\t\tif (controller.signal.aborted) {\n\t\t\t\t\t\tthrow new Error(`Search timed out after ${REQUEST_TIMEOUT_MS / 1000}s`);\n\t\t\t\t\t}\n\t\t\t\t\tthrow new Error(`Search response failed: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\t\t}\n\n\t\t\t\tconst text = extractResultText(parseMcpPayload(body));\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text }],\n\t\t\t\t\tdetails: { query, authenticated: apiKey !== undefined },\n\t\t\t\t};\n\t\t\t} finally {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t}\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\tconst query = str(args?.query);\n\t\t\ttext.setText(query ? theme.fg(\"muted\", query) : \"\");\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, _options: ToolRenderResultOptions, theme: Theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\t// Results are text only; images never appear in a search payload.\n\t\t\ttext.setText(theme.fg(\"muted\", getTextOutput(result as never, false)));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport const createWebSearchTool = (options?: WebSearchToolOptions) =>\n\twrapToolDefinition(createWebSearchToolDefinition(options));\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device-auth.d.ts","sourceRoot":"","sources":["../../../src/extensions/gitnexus/device-auth.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,eAAe;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,iBAAiB;AAC5B,iDAAiD;AAC/C,eAAe;AACjB,+DAA+D;GAC7D,eAAe;AACjB,+BAA+B;GAC7B,SAAS,CAAC;AAEb,qBAAa,eAAgB,SAAQ,KAAK;IACzC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,GAAG,UAAU,CAAC;IAE5D,YAAY,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,CAAC,QAAQ,CAAC,EAI7D;CACD;AAED,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,IAAI,MAAM,CAQ1C;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,IAAI,MAAM,CAS1C;AAkCD,2CAA2C;AAC3C,wBAAsB,eAAe,CACpC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GACrF,OAAO,CAAC,eAAe,CAAC,CA4B1B;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CACnC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,eAAe,EACtB,OAAO,GAAE;IACR,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACd,GACJ,OAAO,CAAC,gBAAgB,CAAC,
|
|
1
|
+
{"version":3,"file":"device-auth.d.ts","sourceRoot":"","sources":["../../../src/extensions/gitnexus/device-auth.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,eAAe;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,iBAAiB;AAC5B,iDAAiD;AAC/C,eAAe;AACjB,+DAA+D;GAC7D,eAAe;AACjB,+BAA+B;GAC7B,SAAS,CAAC;AAEb,qBAAa,eAAgB,SAAQ,KAAK;IACzC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,GAAG,UAAU,CAAC;IAE5D,YAAY,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,CAAC,QAAQ,CAAC,EAI7D;CACD;AAED,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,IAAI,MAAM,CAQ1C;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,IAAI,MAAM,CAS1C;AAkCD,2CAA2C;AAC3C,wBAAsB,eAAe,CACpC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GACrF,OAAO,CAAC,eAAe,CAAC,CA4B1B;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CACnC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,eAAe,EACtB,OAAO,GAAE;IACR,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACd,GACJ,OAAO,CAAC,gBAAgB,CAAC,CAgD3B","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { arch, hostname, platform, userInfo } from \"node:os\";\nimport { APP_NAME, VERSION } from \"../../config.ts\";\n\n/**\n * Device authorization client for hub login.\n *\n * The CLI cannot receive a browser redirect, so it starts a request, shows the\n * user a short code, and polls until they approve it in the web app. The hub\n * mints the token on the first successful poll, so it exists in plaintext only\n * in that one response and in the local auth file.\n *\n * Protocol shape follows RFC 8628, and the hub returns its error codes\n * verbatim so callers branch on a stable string rather than prose.\n */\n\nexport interface DeviceAuthStart {\n\tdeviceCode: string;\n\tuserCode: string;\n\tverificationUrl: string;\n\tverificationUrlComplete?: string;\n\t/** Seconds until the request expires. */\n\texpiresIn: number;\n\t/** Seconds the CLI must wait between polls. */\n\tinterval: number;\n}\n\nexport interface DeviceAuthResult {\n\ttoken: string;\n\thubUrl: string;\n\ttokenId?: string;\n}\n\nexport type DeviceAuthFailure =\n\t/** The user rejected the code in the browser. */\n\t| \"access_denied\"\n\t/** The request expired, was already used, or never existed. */\n\t| \"expired_token\"\n\t/** The CLI gave up waiting. */\n\t| \"timeout\";\n\nexport class DeviceAuthError extends Error {\n\treadonly reason: DeviceAuthFailure | \"network\" | \"protocol\";\n\n\tconstructor(message: string, reason: DeviceAuthError[\"reason\"]) {\n\t\tsuper(message);\n\t\tthis.name = \"DeviceAuthError\";\n\t\tthis.reason = reason;\n\t}\n}\n\n/** Base URL of the hub's HTTP API, derived from the configured MCP endpoint. */\nexport function hubApiBase(hubUrl: string): string {\n\treturn hubUrl.replace(/\\/mcp\\/?$/, \"\").replace(/\\/+$/, \"\");\n}\n\n/** Human-readable device label, so tokens are identifiable in the hub UI. */\nexport function defaultDeviceName(): string {\n\ttry {\n\t\tconst host = hostname();\n\t\tconst user = userInfo().username;\n\t\treturn user ? `${user}@${host}` : host;\n\t} catch {\n\t\treturn \"unknown-device\";\n\t}\n}\n\n/** Stable machine identity required by Hub device-token authentication. */\nexport function deviceFingerprint(): string {\n\tlet username = \"unknown-user\";\n\ttry {\n\t\tusername = userInfo().username || username;\n\t} catch {\n\t\t// Keep deterministic fallback.\n\t}\n\tconst identity = `${hostname()}:${username}:${platform()}:${arch()}`;\n\treturn createHash(\"sha256\").update(identity).digest(\"hex\").slice(0, 16);\n}\n\nasync function postJson(\n\turl: string,\n\tbody: unknown,\n\tsignal: AbortSignal | undefined,\n): Promise<{ status: number; data: any }> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\tbody: JSON.stringify(body),\n\t\t\tsignal,\n\t\t});\n\t} catch (error) {\n\t\tif (signal?.aborted) throw new DeviceAuthError(\"Login cancelled\", \"timeout\");\n\t\tthrow new DeviceAuthError(`Cannot reach the hub at ${url}: ${errorText(error)}`, \"network\");\n\t}\n\n\tconst text = await response.text();\n\tlet data: any;\n\ttry {\n\t\tdata = text ? JSON.parse(text) : {};\n\t} catch {\n\t\tthrow new DeviceAuthError(`Hub returned a non-JSON response (HTTP ${response.status})`, \"protocol\");\n\t}\n\treturn { status: response.status, data };\n}\n\nfunction errorText(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\n/** Ask the hub to begin a device login. */\nexport async function startDeviceAuth(\n\thubUrl: string,\n\toptions: { deviceName?: string; deviceFingerprint?: string; signal?: AbortSignal } = {},\n): Promise<DeviceAuthStart> {\n\tconst { status, data } = await postJson(\n\t\t`${hubApiBase(hubUrl)}/auth/device/start`,\n\t\t{\n\t\t\tagentName: APP_NAME,\n\t\t\tagentVersion: VERSION,\n\t\t\tdeviceName: options.deviceName ?? defaultDeviceName(),\n\t\t\tdeviceFingerprint: options.deviceFingerprint ?? deviceFingerprint(),\n\t\t},\n\t\toptions.signal,\n\t);\n\n\tif (status !== 200 || typeof data?.deviceCode !== \"string\" || typeof data?.userCode !== \"string\") {\n\t\tthrow new DeviceAuthError(\n\t\t\tdata?.error ? String(data.error) : `Hub rejected the login request (HTTP ${status})`,\n\t\t\t\"protocol\",\n\t\t);\n\t}\n\n\treturn {\n\t\tdeviceCode: data.deviceCode,\n\t\tuserCode: data.userCode,\n\t\tverificationUrl: String(data.verificationUrl ?? `${hubApiBase(hubUrl)}/device`),\n\t\tverificationUrlComplete:\n\t\t\ttypeof data.verificationUrlComplete === \"string\" ? data.verificationUrlComplete : undefined,\n\t\texpiresIn: typeof data.expiresIn === \"number\" ? data.expiresIn : 600,\n\t\tinterval: typeof data.interval === \"number\" && data.interval > 0 ? data.interval : 5,\n\t};\n}\n\n/**\n * Poll until the user approves, the request dies, or the deadline passes.\n *\n * `sleep` is injectable so tests do not wait in real time.\n */\nexport async function pollDeviceAuth(\n\thubUrl: string,\n\tstart: DeviceAuthStart,\n\toptions: {\n\t\tsignal?: AbortSignal;\n\t\tsleep?: (ms: number) => Promise<void>;\n\t\tnow?: () => number;\n\t} = {},\n): Promise<DeviceAuthResult> {\n\tconst sleep = options.sleep ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));\n\tconst now = options.now ?? (() => Date.now());\n\tconst deadline = now() + start.expiresIn * 1000;\n\tlet intervalMs = start.interval * 1000;\n\n\twhile (true) {\n\t\tif (options.signal?.aborted) throw new DeviceAuthError(\"Login cancelled\", \"timeout\");\n\t\tawait sleep(intervalMs);\n\t\t// Re-check after sleeping: a request that expired while we waited should\n\t\t// not cost the hub another round trip.\n\t\tif (now() >= deadline) break;\n\n\t\tconst { status, data } = await postJson(\n\t\t\t`${hubApiBase(hubUrl)}/auth/device/poll`,\n\t\t\t{ deviceCode: start.deviceCode },\n\t\t\toptions.signal,\n\t\t);\n\n\t\tif (status === 200 && typeof data?.token === \"string\") {\n\t\t\treturn {\n\t\t\t\ttoken: data.token,\n\t\t\t\thubUrl: typeof data.hubUrl === \"string\" ? data.hubUrl : hubUrl,\n\t\t\t\ttokenId: typeof data.tokenId === \"string\" ? data.tokenId : undefined,\n\t\t\t};\n\t\t}\n\n\t\tswitch (data?.error) {\n\t\t\tcase \"authorization_pending\":\n\t\t\t\tbreak;\n\t\t\tcase \"slow_down\":\n\t\t\t\t// Back off to whatever the hub now advertises, or a second more.\n\t\t\t\tintervalMs =\n\t\t\t\t\ttypeof data.interval === \"number\" && data.interval > 0 ? data.interval * 1000 : intervalMs + 1000;\n\t\t\t\tbreak;\n\t\t\tcase \"access_denied\":\n\t\t\t\tthrow new DeviceAuthError(\"Login was denied in the browser\", \"access_denied\");\n\t\t\tcase \"expired_token\":\n\t\t\t\tthrow new DeviceAuthError(\"The login code expired or was already used\", \"expired_token\");\n\t\t\tdefault:\n\t\t\t\tthrow new DeviceAuthError(\n\t\t\t\t\tdata?.error ? String(data.error) : `Unexpected hub response (HTTP ${status})`,\n\t\t\t\t\t\"protocol\",\n\t\t\t\t);\n\t\t}\n\t}\n\n\tthrow new DeviceAuthError(\"Timed out waiting for approval\", \"timeout\");\n}\n"]}
|
|
@@ -115,7 +115,8 @@ export async function pollDeviceAuth(hubUrl, start, options = {}) {
|
|
|
115
115
|
break;
|
|
116
116
|
case "slow_down":
|
|
117
117
|
// Back off to whatever the hub now advertises, or a second more.
|
|
118
|
-
intervalMs =
|
|
118
|
+
intervalMs =
|
|
119
|
+
typeof data.interval === "number" && data.interval > 0 ? data.interval * 1000 : intervalMs + 1000;
|
|
119
120
|
break;
|
|
120
121
|
case "access_denied":
|
|
121
122
|
throw new DeviceAuthError("Login was denied in the browser", "access_denied");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device-auth.js","sourceRoot":"","sources":["../../../src/extensions/gitnexus/device-auth.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { arch, hostname, platform, userInfo } from \"node:os\";\nimport { APP_NAME, VERSION } from \"../../config.ts\";\n\n/**\n * Device authorization client for hub login.\n *\n * The CLI cannot receive a browser redirect, so it starts a request, shows the\n * user a short code, and polls until they approve it in the web app. The hub\n * mints the token on the first successful poll, so it exists in plaintext only\n * in that one response and in the local auth file.\n *\n * Protocol shape follows RFC 8628, and the hub returns its error codes\n * verbatim so callers branch on a stable string rather than prose.\n */\n\nexport interface DeviceAuthStart {\n\tdeviceCode: string;\n\tuserCode: string;\n\tverificationUrl: string;\n\tverificationUrlComplete?: string;\n\t/** Seconds until the request expires. */\n\texpiresIn: number;\n\t/** Seconds the CLI must wait between polls. */\n\tinterval: number;\n}\n\nexport interface DeviceAuthResult {\n\ttoken: string;\n\thubUrl: string;\n\ttokenId?: string;\n}\n\nexport type DeviceAuthFailure =\n\t/** The user rejected the code in the browser. */\n\t| \"access_denied\"\n\t/** The request expired, was already used, or never existed. */\n\t| \"expired_token\"\n\t/** The CLI gave up waiting. */\n\t| \"timeout\";\n\nexport class DeviceAuthError extends Error {\n\treadonly reason: DeviceAuthFailure | \"network\" | \"protocol\";\n\n\tconstructor(message: string, reason: DeviceAuthError[\"reason\"]) {\n\t\tsuper(message);\n\t\tthis.name = \"DeviceAuthError\";\n\t\tthis.reason = reason;\n\t}\n}\n\n/** Base URL of the hub's HTTP API, derived from the configured MCP endpoint. */\nexport function hubApiBase(hubUrl: string): string {\n\treturn hubUrl.replace(/\\/mcp\\/?$/, \"\").replace(/\\/+$/, \"\");\n}\n\n/** Human-readable device label, so tokens are identifiable in the hub UI. */\nexport function defaultDeviceName(): string {\n\ttry {\n\t\tconst host = hostname();\n\t\tconst user = userInfo().username;\n\t\treturn user ? `${user}@${host}` : host;\n\t} catch {\n\t\treturn \"unknown-device\";\n\t}\n}\n\n/** Stable machine identity required by Hub device-token authentication. */\nexport function deviceFingerprint(): string {\n\tlet username = \"unknown-user\";\n\ttry {\n\t\tusername = userInfo().username || username;\n\t} catch {\n\t\t// Keep deterministic fallback.\n\t}\n\tconst identity = `${hostname()}:${username}:${platform()}:${arch()}`;\n\treturn createHash(\"sha256\").update(identity).digest(\"hex\").slice(0, 16);\n}\n\nasync function postJson(\n\turl: string,\n\tbody: unknown,\n\tsignal: AbortSignal | undefined,\n): Promise<{ status: number; data: any }> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\tbody: JSON.stringify(body),\n\t\t\tsignal,\n\t\t});\n\t} catch (error) {\n\t\tif (signal?.aborted) throw new DeviceAuthError(\"Login cancelled\", \"timeout\");\n\t\tthrow new DeviceAuthError(`Cannot reach the hub at ${url}: ${errorText(error)}`, \"network\");\n\t}\n\n\tconst text = await response.text();\n\tlet data: any;\n\ttry {\n\t\tdata = text ? JSON.parse(text) : {};\n\t} catch {\n\t\tthrow new DeviceAuthError(`Hub returned a non-JSON response (HTTP ${response.status})`, \"protocol\");\n\t}\n\treturn { status: response.status, data };\n}\n\nfunction errorText(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\n/** Ask the hub to begin a device login. */\nexport async function startDeviceAuth(\n\thubUrl: string,\n\toptions: { deviceName?: string; deviceFingerprint?: string; signal?: AbortSignal } = {},\n): Promise<DeviceAuthStart> {\n\tconst { status, data } = await postJson(\n\t\t`${hubApiBase(hubUrl)}/auth/device/start`,\n\t\t{\n\t\t\tagentName: APP_NAME,\n\t\t\tagentVersion: VERSION,\n\t\t\tdeviceName: options.deviceName ?? defaultDeviceName(),\n\t\t\tdeviceFingerprint: options.deviceFingerprint ?? deviceFingerprint(),\n\t\t},\n\t\toptions.signal,\n\t);\n\n\tif (status !== 200 || typeof data?.deviceCode !== \"string\" || typeof data?.userCode !== \"string\") {\n\t\tthrow new DeviceAuthError(\n\t\t\tdata?.error ? String(data.error) : `Hub rejected the login request (HTTP ${status})`,\n\t\t\t\"protocol\",\n\t\t);\n\t}\n\n\treturn {\n\t\tdeviceCode: data.deviceCode,\n\t\tuserCode: data.userCode,\n\t\tverificationUrl: String(data.verificationUrl ?? `${hubApiBase(hubUrl)}/device`),\n\t\tverificationUrlComplete:\n\t\t\ttypeof data.verificationUrlComplete === \"string\" ? data.verificationUrlComplete : undefined,\n\t\texpiresIn: typeof data.expiresIn === \"number\" ? data.expiresIn : 600,\n\t\tinterval: typeof data.interval === \"number\" && data.interval > 0 ? data.interval : 5,\n\t};\n}\n\n/**\n * Poll until the user approves, the request dies, or the deadline passes.\n *\n * `sleep` is injectable so tests do not wait in real time.\n */\nexport async function pollDeviceAuth(\n\thubUrl: string,\n\tstart: DeviceAuthStart,\n\toptions: {\n\t\tsignal?: AbortSignal;\n\t\tsleep?: (ms: number) => Promise<void>;\n\t\tnow?: () => number;\n\t} = {},\n): Promise<DeviceAuthResult> {\n\tconst sleep = options.sleep ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));\n\tconst now = options.now ?? (() => Date.now());\n\tconst deadline = now() + start.expiresIn * 1000;\n\tlet intervalMs = start.interval * 1000;\n\n\twhile (true) {\n\t\tif (options.signal?.aborted) throw new DeviceAuthError(\"Login cancelled\", \"timeout\");\n\t\tawait sleep(intervalMs);\n\t\t// Re-check after sleeping: a request that expired while we waited should\n\t\t// not cost the hub another round trip.\n\t\tif (now() >= deadline) break;\n\n\t\tconst { status, data } = await postJson(\n\t\t\t`${hubApiBase(hubUrl)}/auth/device/poll`,\n\t\t\t{ deviceCode: start.deviceCode },\n\t\t\toptions.signal,\n\t\t);\n\n\t\tif (status === 200 && typeof data?.token === \"string\") {\n\t\t\treturn {\n\t\t\t\ttoken: data.token,\n\t\t\t\thubUrl: typeof data.hubUrl === \"string\" ? data.hubUrl : hubUrl,\n\t\t\t\ttokenId: typeof data.tokenId === \"string\" ? data.tokenId : undefined,\n\t\t\t};\n\t\t}\n\n\t\tswitch (data?.error) {\n\t\t\tcase \"authorization_pending\":\n\t\t\t\tbreak;\n\t\t\tcase \"slow_down\":\n\t\t\t\t// Back off to whatever the hub now advertises, or a second more.\n\t\t\t\tintervalMs = typeof data.interval === \"number\" ? data.interval * 1000 : intervalMs + 1000;\n\t\t\t\tbreak;\n\t\t\tcase \"access_denied\":\n\t\t\t\tthrow new DeviceAuthError(\"Login was denied in the browser\", \"access_denied\");\n\t\t\tcase \"expired_token\":\n\t\t\t\tthrow new DeviceAuthError(\"The login code expired or was already used\", \"expired_token\");\n\t\t\tdefault:\n\t\t\t\tthrow new DeviceAuthError(\n\t\t\t\t\tdata?.error ? String(data.error) : `Unexpected hub response (HTTP ${status})`,\n\t\t\t\t\t\"protocol\",\n\t\t\t\t);\n\t\t}\n\t}\n\n\tthrow new DeviceAuthError(\"Timed out waiting for approval\", \"timeout\");\n}\n"]}
|
|
1
|
+
{"version":3,"file":"device-auth.js","sourceRoot":"","sources":["../../../src/extensions/gitnexus/device-auth.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { arch, hostname, platform, userInfo } from \"node:os\";\nimport { APP_NAME, VERSION } from \"../../config.ts\";\n\n/**\n * Device authorization client for hub login.\n *\n * The CLI cannot receive a browser redirect, so it starts a request, shows the\n * user a short code, and polls until they approve it in the web app. The hub\n * mints the token on the first successful poll, so it exists in plaintext only\n * in that one response and in the local auth file.\n *\n * Protocol shape follows RFC 8628, and the hub returns its error codes\n * verbatim so callers branch on a stable string rather than prose.\n */\n\nexport interface DeviceAuthStart {\n\tdeviceCode: string;\n\tuserCode: string;\n\tverificationUrl: string;\n\tverificationUrlComplete?: string;\n\t/** Seconds until the request expires. */\n\texpiresIn: number;\n\t/** Seconds the CLI must wait between polls. */\n\tinterval: number;\n}\n\nexport interface DeviceAuthResult {\n\ttoken: string;\n\thubUrl: string;\n\ttokenId?: string;\n}\n\nexport type DeviceAuthFailure =\n\t/** The user rejected the code in the browser. */\n\t| \"access_denied\"\n\t/** The request expired, was already used, or never existed. */\n\t| \"expired_token\"\n\t/** The CLI gave up waiting. */\n\t| \"timeout\";\n\nexport class DeviceAuthError extends Error {\n\treadonly reason: DeviceAuthFailure | \"network\" | \"protocol\";\n\n\tconstructor(message: string, reason: DeviceAuthError[\"reason\"]) {\n\t\tsuper(message);\n\t\tthis.name = \"DeviceAuthError\";\n\t\tthis.reason = reason;\n\t}\n}\n\n/** Base URL of the hub's HTTP API, derived from the configured MCP endpoint. */\nexport function hubApiBase(hubUrl: string): string {\n\treturn hubUrl.replace(/\\/mcp\\/?$/, \"\").replace(/\\/+$/, \"\");\n}\n\n/** Human-readable device label, so tokens are identifiable in the hub UI. */\nexport function defaultDeviceName(): string {\n\ttry {\n\t\tconst host = hostname();\n\t\tconst user = userInfo().username;\n\t\treturn user ? `${user}@${host}` : host;\n\t} catch {\n\t\treturn \"unknown-device\";\n\t}\n}\n\n/** Stable machine identity required by Hub device-token authentication. */\nexport function deviceFingerprint(): string {\n\tlet username = \"unknown-user\";\n\ttry {\n\t\tusername = userInfo().username || username;\n\t} catch {\n\t\t// Keep deterministic fallback.\n\t}\n\tconst identity = `${hostname()}:${username}:${platform()}:${arch()}`;\n\treturn createHash(\"sha256\").update(identity).digest(\"hex\").slice(0, 16);\n}\n\nasync function postJson(\n\turl: string,\n\tbody: unknown,\n\tsignal: AbortSignal | undefined,\n): Promise<{ status: number; data: any }> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\tbody: JSON.stringify(body),\n\t\t\tsignal,\n\t\t});\n\t} catch (error) {\n\t\tif (signal?.aborted) throw new DeviceAuthError(\"Login cancelled\", \"timeout\");\n\t\tthrow new DeviceAuthError(`Cannot reach the hub at ${url}: ${errorText(error)}`, \"network\");\n\t}\n\n\tconst text = await response.text();\n\tlet data: any;\n\ttry {\n\t\tdata = text ? JSON.parse(text) : {};\n\t} catch {\n\t\tthrow new DeviceAuthError(`Hub returned a non-JSON response (HTTP ${response.status})`, \"protocol\");\n\t}\n\treturn { status: response.status, data };\n}\n\nfunction errorText(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\n/** Ask the hub to begin a device login. */\nexport async function startDeviceAuth(\n\thubUrl: string,\n\toptions: { deviceName?: string; deviceFingerprint?: string; signal?: AbortSignal } = {},\n): Promise<DeviceAuthStart> {\n\tconst { status, data } = await postJson(\n\t\t`${hubApiBase(hubUrl)}/auth/device/start`,\n\t\t{\n\t\t\tagentName: APP_NAME,\n\t\t\tagentVersion: VERSION,\n\t\t\tdeviceName: options.deviceName ?? defaultDeviceName(),\n\t\t\tdeviceFingerprint: options.deviceFingerprint ?? deviceFingerprint(),\n\t\t},\n\t\toptions.signal,\n\t);\n\n\tif (status !== 200 || typeof data?.deviceCode !== \"string\" || typeof data?.userCode !== \"string\") {\n\t\tthrow new DeviceAuthError(\n\t\t\tdata?.error ? String(data.error) : `Hub rejected the login request (HTTP ${status})`,\n\t\t\t\"protocol\",\n\t\t);\n\t}\n\n\treturn {\n\t\tdeviceCode: data.deviceCode,\n\t\tuserCode: data.userCode,\n\t\tverificationUrl: String(data.verificationUrl ?? `${hubApiBase(hubUrl)}/device`),\n\t\tverificationUrlComplete:\n\t\t\ttypeof data.verificationUrlComplete === \"string\" ? data.verificationUrlComplete : undefined,\n\t\texpiresIn: typeof data.expiresIn === \"number\" ? data.expiresIn : 600,\n\t\tinterval: typeof data.interval === \"number\" && data.interval > 0 ? data.interval : 5,\n\t};\n}\n\n/**\n * Poll until the user approves, the request dies, or the deadline passes.\n *\n * `sleep` is injectable so tests do not wait in real time.\n */\nexport async function pollDeviceAuth(\n\thubUrl: string,\n\tstart: DeviceAuthStart,\n\toptions: {\n\t\tsignal?: AbortSignal;\n\t\tsleep?: (ms: number) => Promise<void>;\n\t\tnow?: () => number;\n\t} = {},\n): Promise<DeviceAuthResult> {\n\tconst sleep = options.sleep ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));\n\tconst now = options.now ?? (() => Date.now());\n\tconst deadline = now() + start.expiresIn * 1000;\n\tlet intervalMs = start.interval * 1000;\n\n\twhile (true) {\n\t\tif (options.signal?.aborted) throw new DeviceAuthError(\"Login cancelled\", \"timeout\");\n\t\tawait sleep(intervalMs);\n\t\t// Re-check after sleeping: a request that expired while we waited should\n\t\t// not cost the hub another round trip.\n\t\tif (now() >= deadline) break;\n\n\t\tconst { status, data } = await postJson(\n\t\t\t`${hubApiBase(hubUrl)}/auth/device/poll`,\n\t\t\t{ deviceCode: start.deviceCode },\n\t\t\toptions.signal,\n\t\t);\n\n\t\tif (status === 200 && typeof data?.token === \"string\") {\n\t\t\treturn {\n\t\t\t\ttoken: data.token,\n\t\t\t\thubUrl: typeof data.hubUrl === \"string\" ? data.hubUrl : hubUrl,\n\t\t\t\ttokenId: typeof data.tokenId === \"string\" ? data.tokenId : undefined,\n\t\t\t};\n\t\t}\n\n\t\tswitch (data?.error) {\n\t\t\tcase \"authorization_pending\":\n\t\t\t\tbreak;\n\t\t\tcase \"slow_down\":\n\t\t\t\t// Back off to whatever the hub now advertises, or a second more.\n\t\t\t\tintervalMs =\n\t\t\t\t\ttypeof data.interval === \"number\" && data.interval > 0 ? data.interval * 1000 : intervalMs + 1000;\n\t\t\t\tbreak;\n\t\t\tcase \"access_denied\":\n\t\t\t\tthrow new DeviceAuthError(\"Login was denied in the browser\", \"access_denied\");\n\t\t\tcase \"expired_token\":\n\t\t\t\tthrow new DeviceAuthError(\"The login code expired or was already used\", \"expired_token\");\n\t\t\tdefault:\n\t\t\t\tthrow new DeviceAuthError(\n\t\t\t\t\tdata?.error ? String(data.error) : `Unexpected hub response (HTTP ${status})`,\n\t\t\t\t\t\"protocol\",\n\t\t\t\t);\n\t\t}\n\t}\n\n\tthrow new DeviceAuthError(\"Timed out waiting for approval\", \"timeout\");\n}\n"]}
|
|
@@ -28,8 +28,10 @@ export interface GatewayPolicy {
|
|
|
28
28
|
usage?: {
|
|
29
29
|
used: number;
|
|
30
30
|
limit: number;
|
|
31
|
+
remaining?: number;
|
|
31
32
|
};
|
|
32
33
|
}
|
|
34
|
+
export declare function formatCreditBalance(policy: GatewayPolicy | undefined): string | undefined;
|
|
33
35
|
export declare function gatewayCachePath(): string;
|
|
34
36
|
/** Cached policy from the last successful refresh, or undefined. */
|
|
35
37
|
export declare function readGatewayCache(path?: string): GatewayPolicy | undefined;
|