@directive-run/ai 0.8.2 → 0.8.4
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/LICENSE +5 -0
- package/dist/anthropic.cjs +3 -3
- package/dist/anthropic.cjs.map +1 -1
- package/dist/anthropic.d.cts +12 -0
- package/dist/anthropic.d.ts +12 -0
- package/dist/anthropic.js +3 -3
- package/dist/anthropic.js.map +1 -1
- package/dist/gemini.cjs +3 -3
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.d.cts +12 -0
- package/dist/gemini.d.ts +12 -0
- package/dist/gemini.js +3 -3
- package/dist/gemini.js.map +1 -1
- package/dist/ollama.cjs +2 -2
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.d.cts +65 -2
- package/dist/ollama.d.ts +65 -2
- package/dist/ollama.js +2 -2
- package/dist/ollama.js.map +1 -1
- package/dist/openai.cjs +3 -3
- package/dist/openai.cjs.map +1 -1
- package/dist/openai.d.cts +30 -0
- package/dist/openai.d.ts +30 -0
- package/dist/openai.js +3 -3
- package/dist/openai.js.map +1 -1
- package/package.json +15 -6
package/dist/ollama.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/agent-utils.ts","../src/adapters/ollama.ts"],"names":["ALLOWED_PROTOCOLS","validateBaseURL","baseURL","url","err","createRunner","options","fetchFn","buildRequest","parseResponse","parseOutput","hooks","parse","text","agent","input","runOptions","startTime","messages","init","fetchInit","response","errBody","parsed","tokenUsage","assistantMessage","allMessages","durationMs","createOllamaRunner","model","timeoutMs","_input","m","res","data","inputTokens","outputTokens"],"mappings":"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","file":"ollama.cjs","sourcesContent":["/**\n * Agent utilities — createRunner, estimateCost, state queries, URL validation.\n */\n\nimport type {\n AdapterHooks,\n AgentLike,\n AgentRunner,\n AgentState,\n ApprovalState,\n Message,\n RunOptions,\n RunResult,\n TokenUsage,\n} from \"./types.js\";\n\n// ============================================================================\n// State Query Helpers\n// ============================================================================\n\n/**\n * Check whether an agent is currently executing a run.\n *\n * @param state - The current {@link AgentState} to inspect.\n * @returns `true` when the agent status is `\"running\"`.\n */\nexport function isAgentRunning(state: AgentState): boolean {\n return state.status === \"running\";\n}\n\n/**\n * Check whether there are tool-call approvals waiting for user confirmation.\n *\n * @param state - The current {@link ApprovalState} to inspect.\n * @returns `true` when one or more approvals are pending.\n */\nexport function hasPendingApprovals(state: ApprovalState): boolean {\n return state.pending.length > 0;\n}\n\n// ============================================================================\n// Cost Estimation\n// ============================================================================\n\n/**\n * Estimate the dollar cost of an agent run based on total token usage.\n *\n * @remarks\n * No default rate is provided — callers must supply the current per-million-token\n * price to avoid silently using stale pricing.\n *\n * @param tokenUsage - Total number of tokens consumed (input + output).\n * @param ratePerMillionTokens - Cost in dollars per one million tokens.\n * @returns Estimated cost in dollars.\n */\nexport function estimateCost(\n tokenUsage: number,\n ratePerMillionTokens: number,\n): number {\n return (tokenUsage / 1_000_000) * ratePerMillionTokens;\n}\n\n// ============================================================================\n// Validation Helpers\n// ============================================================================\n\nconst ALLOWED_PROTOCOLS = new Set([\"http:\", \"https:\"]);\n\n/**\n * Validate that a base URL uses the `http:` or `https:` protocol.\n * Throws immediately at adapter creation time (not at call time) to surface\n * configuration errors before any LLM requests are made.\n *\n * @param baseURL - The base URL string to validate.\n * @throws When the URL is malformed or uses a protocol other than `http:` or `https:`.\n */\nexport function validateBaseURL(baseURL: string): void {\n try {\n const url = new URL(baseURL);\n if (!ALLOWED_PROTOCOLS.has(url.protocol)) {\n throw new Error(\n `[Directive] Invalid baseURL protocol \"${url.protocol}\" – only http: and https: are allowed`,\n );\n }\n } catch (err) {\n if (err instanceof Error && err.message.startsWith(\"[Directive]\")) {\n throw err;\n }\n\n throw new Error(\n `[Directive] Invalid baseURL \"${baseURL}\" – must be a valid URL (e.g. \"https://api.openai.com/v1\")`,\n );\n }\n}\n\n// ============================================================================\n// createRunner Helper\n// ============================================================================\n\n/** Parsed response from an LLM provider */\nexport interface ParsedResponse {\n text: string;\n totalTokens: number;\n /** Input token count, when available from the provider */\n inputTokens?: number;\n /** Output token count, when available from the provider */\n outputTokens?: number;\n}\n\n/** Options for creating an AgentRunner from buildRequest/parseResponse */\nexport interface CreateRunnerOptions {\n fetch?: typeof globalThis.fetch;\n buildRequest: (\n agent: AgentLike,\n input: string,\n messages: Message[],\n ) => { url: string; init: RequestInit };\n parseResponse: (\n response: Response,\n messages: Message[],\n ) => Promise<ParsedResponse>;\n parseOutput?: <T>(text: string) => T;\n /** Lifecycle hooks for tracing, logging, and metrics */\n hooks?: AdapterHooks;\n}\n\n/**\n * Create an {@link AgentRunner} from `buildRequest`/`parseResponse` helpers, reducing\n * ~50 lines of fetch boilerplate to ~20 lines of configuration.\n *\n * @remarks\n * Supports lifecycle hooks for observability:\n * - `onBeforeCall` fires before each API request\n * - `onAfterCall` fires after a successful response (includes token breakdown)\n * - `onError` fires when the request fails\n *\n * Output parsing defaults to `JSON.parse` with a string fallback. Supply a custom\n * `parseOutput` to override (e.g. for structured output schemas).\n *\n * @param options - Configuration for the runner, including request building, response parsing, and hooks.\n * @returns An {@link AgentRunner} function that performs LLM calls via fetch.\n *\n * @example\n * ```typescript\n * const runClaude = createRunner({\n * buildRequest: (agent, input) => ({\n * url: \"/api/claude\",\n * init: {\n * method: \"POST\",\n * headers: { \"Content-Type\": \"application/json\" },\n * body: JSON.stringify({\n * model: agent.model ?? \"claude-haiku-4-5-20251001\",\n * system: agent.instructions ?? \"\",\n * messages: [{ role: \"user\", content: input }],\n * }),\n * },\n * }),\n * parseResponse: async (res) => {\n * const data = await res.json();\n * const inputTokens = data.usage?.input_tokens ?? 0;\n * const outputTokens = data.usage?.output_tokens ?? 0;\n * return {\n * text: data.content?.[0]?.text ?? \"\",\n * totalTokens: inputTokens + outputTokens,\n * inputTokens,\n * outputTokens,\n * };\n * },\n * hooks: {\n * onAfterCall: ({ durationMs, tokenUsage }) => {\n * console.log(`LLM call: ${durationMs}ms, ${tokenUsage.inputTokens}in/${tokenUsage.outputTokens}out`);\n * },\n * },\n * });\n * ```\n *\n * @public\n */\nexport function createRunner(options: CreateRunnerOptions): AgentRunner {\n const {\n fetch: fetchFn = globalThis.fetch,\n buildRequest,\n parseResponse,\n parseOutput,\n hooks,\n } = options;\n\n const defaultParseOutput = <T>(text: string): T => {\n try {\n return JSON.parse(text) as T;\n } catch {\n return text as unknown as T;\n }\n };\n\n const parse = parseOutput ?? defaultParseOutput;\n\n return async <T = unknown>(\n agent: AgentLike,\n input: string,\n runOptions?: RunOptions,\n ): Promise<RunResult<T>> => {\n const startTime = Date.now();\n hooks?.onBeforeCall?.({ agent, input, timestamp: startTime });\n\n const messages: Message[] = [{ role: \"user\", content: input }];\n\n try {\n const { url, init } = buildRequest(agent, input, messages);\n\n const fetchInit: RequestInit = runOptions?.signal\n ? { ...init, signal: runOptions.signal }\n : init;\n\n const response = await fetchFn(url, fetchInit);\n\n if (!response.ok) {\n const errBody = await response.text().catch(() => \"\");\n\n throw new Error(\n `[Directive] AgentRunner request failed: ${response.status} ${response.statusText}${errBody ? ` – ${errBody.slice(0, 300)}` : \"\"}`,\n );\n }\n\n const parsed = await parseResponse(response, messages);\n const tokenUsage: TokenUsage = {\n inputTokens: parsed.inputTokens ?? 0,\n outputTokens: parsed.outputTokens ?? 0,\n };\n\n const assistantMessage: Message = {\n role: \"assistant\",\n content: parsed.text,\n };\n const allMessages: Message[] = [...messages, assistantMessage];\n\n runOptions?.onMessage?.(assistantMessage);\n\n const durationMs = Date.now() - startTime;\n hooks?.onAfterCall?.({\n agent,\n input,\n output: parsed.text,\n totalTokens: parsed.totalTokens,\n tokenUsage,\n durationMs,\n timestamp: Date.now(),\n });\n\n return {\n output: parse<T>(parsed.text),\n messages: allMessages,\n toolCalls: [],\n totalTokens: parsed.totalTokens,\n tokenUsage,\n };\n } catch (err) {\n const durationMs = Date.now() - startTime;\n if (err instanceof Error) {\n hooks?.onError?.({\n agent,\n input,\n error: err,\n durationMs,\n timestamp: Date.now(),\n });\n }\n\n throw err;\n }\n };\n}\n","/**\n * @directive-run/ai/ollama\n *\n * Ollama adapter for Directive AI. Provides runners for local\n * Ollama inference. No API key required.\n *\n * Requires Ollama to be running locally. Start it with: `ollama serve`\n *\n * @example\n * ```typescript\n * import { createOllamaRunner } from '@directive-run/ai/ollama';\n *\n * const runner = createOllamaRunner({ model: 'llama3' });\n * ```\n */\n\nimport { createRunner, validateBaseURL } from \"../agent-utils.js\";\nimport type { AdapterHooks, AgentRunner } from \"../types.js\";\n\n// ============================================================================\n// Ollama Runner\n// ============================================================================\n\n/** Options for createOllamaRunner */\nexport interface OllamaRunnerOptions {\n model?: string;\n baseURL?: string;\n fetch?: typeof globalThis.fetch;\n /** @default undefined */\n timeoutMs?: number;\n /** Lifecycle hooks for tracing, logging, and metrics */\n hooks?: AdapterHooks;\n}\n\n/**\n * Create an AgentRunner for local Ollama inference.\n *\n * Ollama runs locally – no API key or cloud service needed. Default model\n * is `llama3`, default base URL is `http://localhost:11434`.\n *\n * Returns `tokenUsage` with input/output breakdown for cost tracking\n * (useful for monitoring local resource usage).\n *\n * @example\n * ```typescript\n * const runner = createOllamaRunner({ model: \"llama3\" });\n * const orchestrator = createAgentOrchestrator({ runner });\n * const result = await orchestrator.run(agent, input);\n * ```\n */\nexport function createOllamaRunner(\n options: OllamaRunnerOptions = {},\n): AgentRunner {\n const {\n model = \"llama3\",\n baseURL = \"http://localhost:11434\",\n fetch: fetchFn = globalThis.fetch,\n timeoutMs,\n hooks,\n } = options;\n\n validateBaseURL(baseURL);\n\n return createRunner({\n fetch: fetchFn,\n hooks,\n buildRequest: (agent, _input, messages) => ({\n url: `${baseURL}/api/chat`,\n init: {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n model: agent.model ?? model,\n messages: [\n ...(agent.instructions\n ? [{ role: \"system\", content: agent.instructions }]\n : []),\n ...messages.map((m) => ({ role: m.role, content: m.content })),\n ],\n stream: false,\n }),\n ...(timeoutMs != null\n ? { signal: AbortSignal.timeout(timeoutMs) }\n : {}),\n },\n }),\n parseResponse: async (res) => {\n let data: Record<string, unknown>;\n try {\n data = await res.json();\n } catch {\n throw new Error(\n `[Directive] Ollama returned non-JSON response. Is Ollama running at ${baseURL}? Start it with: ollama serve`,\n );\n }\n const text =\n ((data.message as Record<string, unknown>)?.content as string) ?? \"\";\n const inputTokens = (data.prompt_eval_count as number) ?? 0;\n const outputTokens = (data.eval_count as number) ?? 0;\n\n return {\n text,\n totalTokens: inputTokens + outputTokens,\n inputTokens,\n outputTokens,\n };\n },\n });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/agent-utils.ts","../src/adapters/shared.ts","../src/adapters/ollama.ts"],"names":["ALLOWED_PROTOCOLS","validateBaseURL","baseURL","url","err","createRunner","options","fetchFn","buildRequest","parseResponse","parseOutput","hooks","parse","text","agent","input","runOptions","startTime","messages","init","fetchInit","response","errBody","parsed","tokenUsage","assistantMessage","allMessages","durationMs","fireBeforeCallHook","fireAfterCallHook","output","totalTokens","fireErrorHook","buildStreamingResult","fullText","OLLAMA_PRICING","createOllamaRunner","model","timeoutMs","temperature","topP","stop","numPredict","ollamaOptions","hasOptions","_input","m","res","data","inputTokens","outputTokens","createOllamaStreamingRunner","callbacks","reader","decoder","buf","done","value","lines","line","trimmed","chunk","content"],"mappings":"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","file":"ollama.cjs","sourcesContent":["/**\n * Agent utilities — createRunner, estimateCost, state queries, URL validation.\n */\n\nimport type {\n AdapterHooks,\n AgentLike,\n AgentRunner,\n AgentState,\n ApprovalState,\n Message,\n RunOptions,\n RunResult,\n TokenUsage,\n} from \"./types.js\";\n\n// ============================================================================\n// State Query Helpers\n// ============================================================================\n\n/**\n * Check whether an agent is currently executing a run.\n *\n * @param state - The current {@link AgentState} to inspect.\n * @returns `true` when the agent status is `\"running\"`.\n */\nexport function isAgentRunning(state: AgentState): boolean {\n return state.status === \"running\";\n}\n\n/**\n * Check whether there are tool-call approvals waiting for user confirmation.\n *\n * @param state - The current {@link ApprovalState} to inspect.\n * @returns `true` when one or more approvals are pending.\n */\nexport function hasPendingApprovals(state: ApprovalState): boolean {\n return state.pending.length > 0;\n}\n\n// ============================================================================\n// Cost Estimation\n// ============================================================================\n\n/**\n * Estimate the dollar cost of an agent run based on total token usage.\n *\n * @remarks\n * No default rate is provided — callers must supply the current per-million-token\n * price to avoid silently using stale pricing.\n *\n * @param tokenUsage - Total number of tokens consumed (input + output).\n * @param ratePerMillionTokens - Cost in dollars per one million tokens.\n * @returns Estimated cost in dollars.\n */\nexport function estimateCost(\n tokenUsage: number,\n ratePerMillionTokens: number,\n): number {\n return (tokenUsage / 1_000_000) * ratePerMillionTokens;\n}\n\n// ============================================================================\n// Validation Helpers\n// ============================================================================\n\nconst ALLOWED_PROTOCOLS = new Set([\"http:\", \"https:\"]);\n\n/**\n * Validate that a base URL uses the `http:` or `https:` protocol.\n * Throws immediately at adapter creation time (not at call time) to surface\n * configuration errors before any LLM requests are made.\n *\n * @param baseURL - The base URL string to validate.\n * @throws When the URL is malformed or uses a protocol other than `http:` or `https:`.\n */\nexport function validateBaseURL(baseURL: string): void {\n try {\n const url = new URL(baseURL);\n if (!ALLOWED_PROTOCOLS.has(url.protocol)) {\n throw new Error(\n `[Directive] Invalid baseURL protocol \"${url.protocol}\" – only http: and https: are allowed`,\n );\n }\n } catch (err) {\n if (err instanceof Error && err.message.startsWith(\"[Directive]\")) {\n throw err;\n }\n\n throw new Error(\n `[Directive] Invalid baseURL \"${baseURL}\" – must be a valid URL (e.g. \"https://api.openai.com/v1\")`,\n );\n }\n}\n\n// ============================================================================\n// createRunner Helper\n// ============================================================================\n\n/** Parsed response from an LLM provider */\nexport interface ParsedResponse {\n text: string;\n totalTokens: number;\n /** Input token count, when available from the provider */\n inputTokens?: number;\n /** Output token count, when available from the provider */\n outputTokens?: number;\n}\n\n/** Options for creating an AgentRunner from buildRequest/parseResponse */\nexport interface CreateRunnerOptions {\n fetch?: typeof globalThis.fetch;\n buildRequest: (\n agent: AgentLike,\n input: string,\n messages: Message[],\n ) => { url: string; init: RequestInit };\n parseResponse: (\n response: Response,\n messages: Message[],\n ) => Promise<ParsedResponse>;\n parseOutput?: <T>(text: string) => T;\n /** Lifecycle hooks for tracing, logging, and metrics */\n hooks?: AdapterHooks;\n}\n\n/**\n * Create an {@link AgentRunner} from `buildRequest`/`parseResponse` helpers, reducing\n * ~50 lines of fetch boilerplate to ~20 lines of configuration.\n *\n * @remarks\n * Supports lifecycle hooks for observability:\n * - `onBeforeCall` fires before each API request\n * - `onAfterCall` fires after a successful response (includes token breakdown)\n * - `onError` fires when the request fails\n *\n * Output parsing defaults to `JSON.parse` with a string fallback. Supply a custom\n * `parseOutput` to override (e.g. for structured output schemas).\n *\n * @param options - Configuration for the runner, including request building, response parsing, and hooks.\n * @returns An {@link AgentRunner} function that performs LLM calls via fetch.\n *\n * @example\n * ```typescript\n * const runClaude = createRunner({\n * buildRequest: (agent, input) => ({\n * url: \"/api/claude\",\n * init: {\n * method: \"POST\",\n * headers: { \"Content-Type\": \"application/json\" },\n * body: JSON.stringify({\n * model: agent.model ?? \"claude-haiku-4-5-20251001\",\n * system: agent.instructions ?? \"\",\n * messages: [{ role: \"user\", content: input }],\n * }),\n * },\n * }),\n * parseResponse: async (res) => {\n * const data = await res.json();\n * const inputTokens = data.usage?.input_tokens ?? 0;\n * const outputTokens = data.usage?.output_tokens ?? 0;\n * return {\n * text: data.content?.[0]?.text ?? \"\",\n * totalTokens: inputTokens + outputTokens,\n * inputTokens,\n * outputTokens,\n * };\n * },\n * hooks: {\n * onAfterCall: ({ durationMs, tokenUsage }) => {\n * console.log(`LLM call: ${durationMs}ms, ${tokenUsage.inputTokens}in/${tokenUsage.outputTokens}out`);\n * },\n * },\n * });\n * ```\n *\n * @public\n */\nexport function createRunner(options: CreateRunnerOptions): AgentRunner {\n const {\n fetch: fetchFn = globalThis.fetch,\n buildRequest,\n parseResponse,\n parseOutput,\n hooks,\n } = options;\n\n const defaultParseOutput = <T>(text: string): T => {\n try {\n return JSON.parse(text) as T;\n } catch {\n return text as unknown as T;\n }\n };\n\n const parse = parseOutput ?? defaultParseOutput;\n\n return async <T = unknown>(\n agent: AgentLike,\n input: string,\n runOptions?: RunOptions,\n ): Promise<RunResult<T>> => {\n const startTime = Date.now();\n hooks?.onBeforeCall?.({ agent, input, timestamp: startTime });\n\n const messages: Message[] = [{ role: \"user\", content: input }];\n\n try {\n const { url, init } = buildRequest(agent, input, messages);\n\n const fetchInit: RequestInit = runOptions?.signal\n ? { ...init, signal: runOptions.signal }\n : init;\n\n const response = await fetchFn(url, fetchInit);\n\n if (!response.ok) {\n const errBody = await response.text().catch(() => \"\");\n\n throw new Error(\n `[Directive] AgentRunner request failed: ${response.status} ${response.statusText}${errBody ? ` – ${errBody.slice(0, 300)}` : \"\"}`,\n );\n }\n\n const parsed = await parseResponse(response, messages);\n const tokenUsage: TokenUsage = {\n inputTokens: parsed.inputTokens ?? 0,\n outputTokens: parsed.outputTokens ?? 0,\n };\n\n const assistantMessage: Message = {\n role: \"assistant\",\n content: parsed.text,\n };\n const allMessages: Message[] = [...messages, assistantMessage];\n\n runOptions?.onMessage?.(assistantMessage);\n\n const durationMs = Date.now() - startTime;\n hooks?.onAfterCall?.({\n agent,\n input,\n output: parsed.text,\n totalTokens: parsed.totalTokens,\n tokenUsage,\n durationMs,\n timestamp: Date.now(),\n });\n\n return {\n output: parse<T>(parsed.text),\n messages: allMessages,\n toolCalls: [],\n totalTokens: parsed.totalTokens,\n tokenUsage,\n };\n } catch (err) {\n const durationMs = Date.now() - startTime;\n if (err instanceof Error) {\n hooks?.onError?.({\n agent,\n input,\n error: err,\n durationMs,\n timestamp: Date.now(),\n });\n }\n\n throw err;\n }\n };\n}\n","/**\n * Shared utilities for streaming adapters.\n *\n * Extracts common SSE parsing, error handling, hook lifecycle, and response\n * building logic used across Anthropic, OpenAI, and Gemini streaming runners.\n */\n\nimport type {\n AdapterHooks,\n AgentLike,\n Message,\n TokenUsage,\n} from \"../types.js\";\n\n// ============================================================================\n// HTTP Error Handling\n// ============================================================================\n\n/**\n * Throw a standardized HTTP error from a streaming response.\n * Reads up to 200 chars of the error body for diagnostics.\n */\nexport async function throwStreamingHTTPError(\n response: Response,\n adapterName: string,\n): Promise<never> {\n const errBody = await response.text().catch(() => \"\");\n\n throw new Error(\n `[Directive] ${adapterName} streaming error ${response.status}${errBody ? ` – ${errBody.slice(0, 200)}` : \"\"}`,\n );\n}\n\n/**\n * Get an SSE reader from a response, throwing if body is missing.\n */\nexport function getSSEReader(\n response: Response,\n): ReadableStreamDefaultReader<Uint8Array> {\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error(\"[Directive] No response body\");\n }\n\n return reader;\n}\n\n// ============================================================================\n// API Key Validation\n// ============================================================================\n\n/**\n * Warn in non-production environments if an API key is empty.\n */\nexport function warnIfMissingApiKey(\n apiKey: string | undefined,\n functionName: string,\n): void {\n if (\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV !== \"production\" &&\n !apiKey\n ) {\n console.warn(\n `[Directive] ${functionName}: apiKey is empty. API calls will fail.`,\n );\n }\n}\n\n// ============================================================================\n// SSE Stream Parser\n// ============================================================================\n\n/** Result from parsing a single SSE event (provider-specific). */\nexport interface SSEEventResult {\n /** Text token to append to output. */\n text?: string;\n /** Updated input token count (cumulative, not delta). */\n inputTokens?: number;\n /** Updated output token count (cumulative, not delta). */\n outputTokens?: number;\n}\n\n/**\n * Parse an SSE stream from a Response, calling `onToken` for each text chunk\n * and `parseEvent` for provider-specific event extraction.\n *\n * Handles buffering, `[DONE]` sentinels, malformed JSON, and reader cleanup.\n *\n * @param reader - The ReadableStream reader from the response body.\n * @param onToken - Callback for each text token (may be undefined).\n * @param parseEvent - Provider-specific function to extract text and tokens from a parsed SSE event.\n * @param adapterName - Adapter name for dev-mode warnings.\n * @returns The full text output and final token counts.\n */\nexport async function parseSSEStream(\n reader: ReadableStreamDefaultReader<Uint8Array>,\n onToken: ((token: string) => void) | undefined,\n parseEvent: (event: Record<string, unknown>) => SSEEventResult,\n adapterName: string,\n): Promise<{ fullText: string; inputTokens: number; outputTokens: number }> {\n const decoder = new TextDecoder();\n let buf = \"\";\n let fullText = \"\";\n let inputTokens = 0;\n let outputTokens = 0;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n buf += decoder.decode(value, { stream: true });\n const lines = buf.split(\"\\n\");\n buf = lines.pop() ?? \"\";\n\n for (const line of lines) {\n if (!line.startsWith(\"data: \")) {\n continue;\n }\n const data = line.slice(6).trim();\n if (data === \"[DONE]\") {\n continue;\n }\n\n try {\n const event = JSON.parse(data);\n const result = parseEvent(event);\n if (result.text) {\n fullText += result.text;\n onToken?.(result.text);\n }\n if (result.inputTokens !== undefined) {\n inputTokens = result.inputTokens;\n }\n if (result.outputTokens !== undefined) {\n outputTokens = result.outputTokens;\n }\n } catch (parseErr) {\n if (parseErr instanceof SyntaxError) {\n if (\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV === \"development\"\n ) {\n console.warn(\n `[Directive] Malformed SSE event from ${adapterName}:`,\n data,\n );\n }\n } else {\n throw parseErr;\n }\n }\n }\n }\n } finally {\n reader.cancel().catch(() => {});\n }\n\n return { fullText, inputTokens, outputTokens };\n}\n\n// ============================================================================\n// Hook Lifecycle Helpers\n// ============================================================================\n\n/**\n * Fire the onBeforeCall hook and return the start timestamp.\n */\nexport function fireBeforeCallHook(\n hooks: AdapterHooks | undefined,\n agent: AgentLike,\n input: string,\n): number {\n const startTime = Date.now();\n hooks?.onBeforeCall?.({ agent, input, timestamp: startTime });\n\n return startTime;\n}\n\n/**\n * Fire the onAfterCall hook with timing and token data.\n */\nexport function fireAfterCallHook(\n hooks: AdapterHooks | undefined,\n agent: AgentLike,\n input: string,\n output: string,\n totalTokens: number,\n tokenUsage: TokenUsage,\n startTime: number,\n): void {\n hooks?.onAfterCall?.({\n agent,\n input,\n output,\n totalTokens,\n tokenUsage,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n });\n}\n\n/**\n * Fire the onError hook if the error is an Error instance.\n */\nexport function fireErrorHook(\n hooks: AdapterHooks | undefined,\n agent: AgentLike,\n input: string,\n err: unknown,\n startTime: number,\n): void {\n if (err instanceof Error) {\n hooks?.onError?.({\n agent,\n input,\n error: err,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n });\n }\n}\n\n// ============================================================================\n// Streaming Response Builder\n// ============================================================================\n\n/**\n * Build the standard streaming runner return value.\n */\nexport function buildStreamingResult(\n input: string,\n fullText: string,\n totalTokens: number,\n tokenUsage: TokenUsage,\n): {\n output: string;\n messages: Message[];\n toolCalls: never[];\n totalTokens: number;\n tokenUsage: TokenUsage;\n} {\n const assistantMsg: Message = { role: \"assistant\", content: fullText };\n\n return {\n output: fullText,\n messages: [{ role: \"user\" as const, content: input }, assistantMsg],\n toolCalls: [],\n totalTokens,\n tokenUsage,\n };\n}\n","/**\n * @directive-run/ai/ollama\n *\n * Ollama adapter for Directive AI. Provides runners for local\n * Ollama inference. No API key required.\n *\n * Requires Ollama to be running locally. Start it with: `ollama serve`\n *\n * @example\n * ```typescript\n * import { createOllamaRunner } from '@directive-run/ai/ollama';\n *\n * const runner = createOllamaRunner({ model: 'llama3' });\n * ```\n */\n\nimport { createRunner, validateBaseURL } from \"../agent-utils.js\";\nimport type {\n AdapterHooks,\n AgentRunner,\n} from \"../types.js\";\nimport type { StreamingCallbackRunner } from \"../types.js\";\nimport {\n buildStreamingResult,\n fireAfterCallHook,\n fireBeforeCallHook,\n fireErrorHook,\n} from \"./shared.js\";\n\n// ============================================================================\n// Pricing Constants\n// ============================================================================\n\n/**\n * Ollama model pricing (USD per million tokens).\n *\n * Ollama runs locally — all costs are zero. This table is provided for\n * compatibility with `estimateCost()` so local models integrate seamlessly\n * into cost-tracking pipelines.\n *\n * @example\n * ```typescript\n * import { estimateCost } from '@directive-run/ai';\n * import { OLLAMA_PRICING } from '@directive-run/ai/ollama';\n *\n * const cost =\n * estimateCost(result.tokenUsage!.inputTokens, OLLAMA_PRICING[\"llama3\"].input) +\n * estimateCost(result.tokenUsage!.outputTokens, OLLAMA_PRICING[\"llama3\"].output);\n * // → 0\n * ```\n */\nexport const OLLAMA_PRICING: Record<string, { input: number; output: number }> =\n {\n llama3: { input: 0, output: 0 },\n \"llama3.1\": { input: 0, output: 0 },\n \"llama3.2\": { input: 0, output: 0 },\n \"llama3.3\": { input: 0, output: 0 },\n mistral: { input: 0, output: 0 },\n mixtral: { input: 0, output: 0 },\n codellama: { input: 0, output: 0 },\n \"gemma2\": { input: 0, output: 0 },\n phi3: { input: 0, output: 0 },\n qwen2: { input: 0, output: 0 },\n deepseek: { input: 0, output: 0 },\n \"deepseek-coder\": { input: 0, output: 0 },\n \"command-r\": { input: 0, output: 0 },\n };\n\n// ============================================================================\n// Ollama Runner\n// ============================================================================\n\n/** Options for createOllamaRunner */\nexport interface OllamaRunnerOptions {\n model?: string;\n baseURL?: string;\n fetch?: typeof globalThis.fetch;\n /** @default undefined */\n timeoutMs?: number;\n /** Lifecycle hooks for tracing, logging, and metrics */\n hooks?: AdapterHooks;\n /** Sampling temperature. Higher = more random. */\n temperature?: number;\n /** Nucleus sampling: top-P probability mass (0–1). */\n topP?: number;\n /** Stop sequences. The model will stop generating when it encounters one. */\n stop?: string[];\n /** Maximum number of tokens to generate. Ollama uses `num_predict`. */\n numPredict?: number;\n}\n\n/**\n * Create an AgentRunner for local Ollama inference.\n *\n * Ollama runs locally – no API key or cloud service needed. Default model\n * is `llama3`, default base URL is `http://localhost:11434`.\n *\n * Returns `tokenUsage` with input/output breakdown for cost tracking\n * (useful for monitoring local resource usage).\n *\n * @example\n * ```typescript\n * const runner = createOllamaRunner({ model: \"llama3\" });\n * const orchestrator = createAgentOrchestrator({ runner });\n * const result = await orchestrator.run(agent, input);\n * ```\n */\nexport function createOllamaRunner(\n options: OllamaRunnerOptions = {},\n): AgentRunner {\n const {\n model = \"llama3\",\n baseURL = \"http://localhost:11434\",\n fetch: fetchFn = globalThis.fetch,\n timeoutMs,\n hooks,\n temperature,\n topP,\n stop,\n numPredict,\n } = options;\n\n validateBaseURL(baseURL);\n\n const ollamaOptions: Record<string, unknown> = {};\n if (temperature != null) {\n ollamaOptions.temperature = temperature;\n }\n if (topP != null) {\n ollamaOptions.top_p = topP;\n }\n if (stop != null) {\n ollamaOptions.stop = stop;\n }\n if (numPredict != null) {\n ollamaOptions.num_predict = numPredict;\n }\n const hasOptions = Object.keys(ollamaOptions).length > 0;\n\n return createRunner({\n fetch: fetchFn,\n hooks,\n buildRequest: (agent, _input, messages) => ({\n url: `${baseURL}/api/chat`,\n init: {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n model: agent.model ?? model,\n messages: [\n ...(agent.instructions\n ? [{ role: \"system\", content: agent.instructions }]\n : []),\n ...messages.map((m) => ({ role: m.role, content: m.content })),\n ],\n stream: false,\n ...(hasOptions ? { options: ollamaOptions } : {}),\n }),\n ...(timeoutMs != null\n ? { signal: AbortSignal.timeout(timeoutMs) }\n : {}),\n },\n }),\n parseResponse: async (res) => {\n let data: Record<string, unknown>;\n try {\n data = await res.json();\n } catch {\n throw new Error(\n `[Directive] Ollama returned non-JSON response. Is Ollama running at ${baseURL}? Start it with: ollama serve`,\n );\n }\n const text =\n ((data.message as Record<string, unknown>)?.content as string) ?? \"\";\n const inputTokens = (data.prompt_eval_count as number) ?? 0;\n const outputTokens = (data.eval_count as number) ?? 0;\n\n return {\n text,\n totalTokens: inputTokens + outputTokens,\n inputTokens,\n outputTokens,\n };\n },\n });\n}\n\n// ============================================================================\n// Ollama Streaming Runner\n// ============================================================================\n\n/** Options for createOllamaStreamingRunner */\nexport interface OllamaStreamingRunnerOptions {\n model?: string;\n baseURL?: string;\n fetch?: typeof globalThis.fetch;\n /** Lifecycle hooks for tracing, logging, and metrics */\n hooks?: AdapterHooks;\n /** Sampling temperature. Higher = more random. */\n temperature?: number;\n /** Nucleus sampling: top-P probability mass (0–1). */\n topP?: number;\n /** Stop sequences. The model will stop generating when it encounters one. */\n stop?: string[];\n /** Maximum number of tokens to generate. Ollama uses `num_predict`. */\n numPredict?: number;\n}\n\n/**\n * Create a StreamingCallbackRunner for local Ollama inference with\n * chunked JSON streaming. Can be used standalone or paired with `createOllamaRunner`.\n *\n * Ollama streams newline-delimited JSON objects with `{ message: { content }, done }`.\n * Token counts are included in the final chunk (`prompt_eval_count`, `eval_count`).\n *\n * Returns `tokenUsage` with input/output breakdown for resource tracking.\n *\n * @example\n * ```typescript\n * const streamingRunner = createOllamaStreamingRunner({ model: 'llama3' });\n * const streamRunner = createStreamingRunner(streamingRunner);\n * const { stream, result } = streamRunner(agent, input);\n * ```\n */\nexport function createOllamaStreamingRunner(\n options: OllamaStreamingRunnerOptions = {},\n): StreamingCallbackRunner {\n const {\n model = \"llama3\",\n baseURL = \"http://localhost:11434\",\n fetch: fetchFn = globalThis.fetch,\n hooks,\n temperature,\n topP,\n stop,\n numPredict,\n } = options;\n\n validateBaseURL(baseURL);\n\n const ollamaOptions: Record<string, unknown> = {};\n if (temperature != null) {\n ollamaOptions.temperature = temperature;\n }\n if (topP != null) {\n ollamaOptions.top_p = topP;\n }\n if (stop != null) {\n ollamaOptions.stop = stop;\n }\n if (numPredict != null) {\n ollamaOptions.num_predict = numPredict;\n }\n const hasOptions = Object.keys(ollamaOptions).length > 0;\n\n return async (agent, input, callbacks) => {\n const startTime = fireBeforeCallHook(hooks, agent, input);\n\n try {\n const response = await fetchFn(`${baseURL}/api/chat`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n model: agent.model ?? model,\n messages: [\n ...(agent.instructions\n ? [{ role: \"system\", content: agent.instructions }]\n : []),\n { role: \"user\", content: input },\n ],\n stream: true,\n ...(hasOptions ? { options: ollamaOptions } : {}),\n }),\n signal: callbacks.signal,\n });\n\n if (!response.ok) {\n const errBody = await response.text().catch(() => \"\");\n\n throw new Error(\n `[Directive] Ollama streaming error ${response.status}${errBody ? ` – ${errBody.slice(0, 200)}` : \"\"}`,\n );\n }\n\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error(\"[Directive] No response body from Ollama\");\n }\n\n const decoder = new TextDecoder();\n let buf = \"\";\n let fullText = \"\";\n let inputTokens = 0;\n let outputTokens = 0;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n buf += decoder.decode(value, { stream: true });\n const lines = buf.split(\"\\n\");\n buf = lines.pop() ?? \"\";\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) {\n continue;\n }\n\n let chunk: Record<string, unknown>;\n try {\n chunk = JSON.parse(trimmed);\n } catch {\n if (\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV === \"development\"\n ) {\n console.warn(\n \"[Directive] Malformed streaming chunk from Ollama:\",\n trimmed,\n );\n }\n\n continue;\n }\n\n const msg = chunk.message as\n | Record<string, unknown>\n | undefined;\n const content = (msg?.content as string) ?? \"\";\n if (content) {\n fullText += content;\n callbacks.onToken?.(content);\n }\n\n if (chunk.done) {\n inputTokens =\n (chunk.prompt_eval_count as number) ?? 0;\n outputTokens = (chunk.eval_count as number) ?? 0;\n }\n }\n }\n } finally {\n reader.cancel().catch(() => {});\n }\n\n const tokenUsage = { inputTokens, outputTokens };\n const totalTokens = inputTokens + outputTokens;\n\n callbacks.onMessage?.({ role: \"assistant\", content: fullText });\n fireAfterCallHook(hooks, agent, input, fullText, totalTokens, tokenUsage, startTime);\n\n return buildStreamingResult(input, fullText, totalTokens, tokenUsage);\n } catch (err) {\n fireErrorHook(hooks, agent, input, err, startTime);\n\n throw err;\n }\n };\n}\n"]}
|
package/dist/ollama.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as AdapterHooks, b as AgentRunner } from './types-BM-6xZf_.cjs';
|
|
1
|
+
import { a as AdapterHooks, b as AgentRunner, aX as StreamingCallbackRunner } from './types-BM-6xZf_.cjs';
|
|
2
2
|
import '@directive-run/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -17,6 +17,28 @@ import '@directive-run/core';
|
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Ollama model pricing (USD per million tokens).
|
|
22
|
+
*
|
|
23
|
+
* Ollama runs locally — all costs are zero. This table is provided for
|
|
24
|
+
* compatibility with `estimateCost()` so local models integrate seamlessly
|
|
25
|
+
* into cost-tracking pipelines.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* import { estimateCost } from '@directive-run/ai';
|
|
30
|
+
* import { OLLAMA_PRICING } from '@directive-run/ai/ollama';
|
|
31
|
+
*
|
|
32
|
+
* const cost =
|
|
33
|
+
* estimateCost(result.tokenUsage!.inputTokens, OLLAMA_PRICING["llama3"].input) +
|
|
34
|
+
* estimateCost(result.tokenUsage!.outputTokens, OLLAMA_PRICING["llama3"].output);
|
|
35
|
+
* // → 0
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare const OLLAMA_PRICING: Record<string, {
|
|
39
|
+
input: number;
|
|
40
|
+
output: number;
|
|
41
|
+
}>;
|
|
20
42
|
/** Options for createOllamaRunner */
|
|
21
43
|
interface OllamaRunnerOptions {
|
|
22
44
|
model?: string;
|
|
@@ -26,6 +48,14 @@ interface OllamaRunnerOptions {
|
|
|
26
48
|
timeoutMs?: number;
|
|
27
49
|
/** Lifecycle hooks for tracing, logging, and metrics */
|
|
28
50
|
hooks?: AdapterHooks;
|
|
51
|
+
/** Sampling temperature. Higher = more random. */
|
|
52
|
+
temperature?: number;
|
|
53
|
+
/** Nucleus sampling: top-P probability mass (0–1). */
|
|
54
|
+
topP?: number;
|
|
55
|
+
/** Stop sequences. The model will stop generating when it encounters one. */
|
|
56
|
+
stop?: string[];
|
|
57
|
+
/** Maximum number of tokens to generate. Ollama uses `num_predict`. */
|
|
58
|
+
numPredict?: number;
|
|
29
59
|
}
|
|
30
60
|
/**
|
|
31
61
|
* Create an AgentRunner for local Ollama inference.
|
|
@@ -44,5 +74,38 @@ interface OllamaRunnerOptions {
|
|
|
44
74
|
* ```
|
|
45
75
|
*/
|
|
46
76
|
declare function createOllamaRunner(options?: OllamaRunnerOptions): AgentRunner;
|
|
77
|
+
/** Options for createOllamaStreamingRunner */
|
|
78
|
+
interface OllamaStreamingRunnerOptions {
|
|
79
|
+
model?: string;
|
|
80
|
+
baseURL?: string;
|
|
81
|
+
fetch?: typeof globalThis.fetch;
|
|
82
|
+
/** Lifecycle hooks for tracing, logging, and metrics */
|
|
83
|
+
hooks?: AdapterHooks;
|
|
84
|
+
/** Sampling temperature. Higher = more random. */
|
|
85
|
+
temperature?: number;
|
|
86
|
+
/** Nucleus sampling: top-P probability mass (0–1). */
|
|
87
|
+
topP?: number;
|
|
88
|
+
/** Stop sequences. The model will stop generating when it encounters one. */
|
|
89
|
+
stop?: string[];
|
|
90
|
+
/** Maximum number of tokens to generate. Ollama uses `num_predict`. */
|
|
91
|
+
numPredict?: number;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Create a StreamingCallbackRunner for local Ollama inference with
|
|
95
|
+
* chunked JSON streaming. Can be used standalone or paired with `createOllamaRunner`.
|
|
96
|
+
*
|
|
97
|
+
* Ollama streams newline-delimited JSON objects with `{ message: { content }, done }`.
|
|
98
|
+
* Token counts are included in the final chunk (`prompt_eval_count`, `eval_count`).
|
|
99
|
+
*
|
|
100
|
+
* Returns `tokenUsage` with input/output breakdown for resource tracking.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* const streamingRunner = createOllamaStreamingRunner({ model: 'llama3' });
|
|
105
|
+
* const streamRunner = createStreamingRunner(streamingRunner);
|
|
106
|
+
* const { stream, result } = streamRunner(agent, input);
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
declare function createOllamaStreamingRunner(options?: OllamaStreamingRunnerOptions): StreamingCallbackRunner;
|
|
47
110
|
|
|
48
|
-
export { type OllamaRunnerOptions, createOllamaRunner };
|
|
111
|
+
export { OLLAMA_PRICING, type OllamaRunnerOptions, type OllamaStreamingRunnerOptions, createOllamaRunner, createOllamaStreamingRunner };
|
package/dist/ollama.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as AdapterHooks, b as AgentRunner } from './types-BM-6xZf_.js';
|
|
1
|
+
import { a as AdapterHooks, b as AgentRunner, aX as StreamingCallbackRunner } from './types-BM-6xZf_.js';
|
|
2
2
|
import '@directive-run/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -17,6 +17,28 @@ import '@directive-run/core';
|
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Ollama model pricing (USD per million tokens).
|
|
22
|
+
*
|
|
23
|
+
* Ollama runs locally — all costs are zero. This table is provided for
|
|
24
|
+
* compatibility with `estimateCost()` so local models integrate seamlessly
|
|
25
|
+
* into cost-tracking pipelines.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* import { estimateCost } from '@directive-run/ai';
|
|
30
|
+
* import { OLLAMA_PRICING } from '@directive-run/ai/ollama';
|
|
31
|
+
*
|
|
32
|
+
* const cost =
|
|
33
|
+
* estimateCost(result.tokenUsage!.inputTokens, OLLAMA_PRICING["llama3"].input) +
|
|
34
|
+
* estimateCost(result.tokenUsage!.outputTokens, OLLAMA_PRICING["llama3"].output);
|
|
35
|
+
* // → 0
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare const OLLAMA_PRICING: Record<string, {
|
|
39
|
+
input: number;
|
|
40
|
+
output: number;
|
|
41
|
+
}>;
|
|
20
42
|
/** Options for createOllamaRunner */
|
|
21
43
|
interface OllamaRunnerOptions {
|
|
22
44
|
model?: string;
|
|
@@ -26,6 +48,14 @@ interface OllamaRunnerOptions {
|
|
|
26
48
|
timeoutMs?: number;
|
|
27
49
|
/** Lifecycle hooks for tracing, logging, and metrics */
|
|
28
50
|
hooks?: AdapterHooks;
|
|
51
|
+
/** Sampling temperature. Higher = more random. */
|
|
52
|
+
temperature?: number;
|
|
53
|
+
/** Nucleus sampling: top-P probability mass (0–1). */
|
|
54
|
+
topP?: number;
|
|
55
|
+
/** Stop sequences. The model will stop generating when it encounters one. */
|
|
56
|
+
stop?: string[];
|
|
57
|
+
/** Maximum number of tokens to generate. Ollama uses `num_predict`. */
|
|
58
|
+
numPredict?: number;
|
|
29
59
|
}
|
|
30
60
|
/**
|
|
31
61
|
* Create an AgentRunner for local Ollama inference.
|
|
@@ -44,5 +74,38 @@ interface OllamaRunnerOptions {
|
|
|
44
74
|
* ```
|
|
45
75
|
*/
|
|
46
76
|
declare function createOllamaRunner(options?: OllamaRunnerOptions): AgentRunner;
|
|
77
|
+
/** Options for createOllamaStreamingRunner */
|
|
78
|
+
interface OllamaStreamingRunnerOptions {
|
|
79
|
+
model?: string;
|
|
80
|
+
baseURL?: string;
|
|
81
|
+
fetch?: typeof globalThis.fetch;
|
|
82
|
+
/** Lifecycle hooks for tracing, logging, and metrics */
|
|
83
|
+
hooks?: AdapterHooks;
|
|
84
|
+
/** Sampling temperature. Higher = more random. */
|
|
85
|
+
temperature?: number;
|
|
86
|
+
/** Nucleus sampling: top-P probability mass (0–1). */
|
|
87
|
+
topP?: number;
|
|
88
|
+
/** Stop sequences. The model will stop generating when it encounters one. */
|
|
89
|
+
stop?: string[];
|
|
90
|
+
/** Maximum number of tokens to generate. Ollama uses `num_predict`. */
|
|
91
|
+
numPredict?: number;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Create a StreamingCallbackRunner for local Ollama inference with
|
|
95
|
+
* chunked JSON streaming. Can be used standalone or paired with `createOllamaRunner`.
|
|
96
|
+
*
|
|
97
|
+
* Ollama streams newline-delimited JSON objects with `{ message: { content }, done }`.
|
|
98
|
+
* Token counts are included in the final chunk (`prompt_eval_count`, `eval_count`).
|
|
99
|
+
*
|
|
100
|
+
* Returns `tokenUsage` with input/output breakdown for resource tracking.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* const streamingRunner = createOllamaStreamingRunner({ model: 'llama3' });
|
|
105
|
+
* const streamRunner = createStreamingRunner(streamingRunner);
|
|
106
|
+
* const { stream, result } = streamRunner(agent, input);
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
declare function createOllamaStreamingRunner(options?: OllamaStreamingRunnerOptions): StreamingCallbackRunner;
|
|
47
110
|
|
|
48
|
-
export { type OllamaRunnerOptions, createOllamaRunner };
|
|
111
|
+
export { OLLAMA_PRICING, type OllamaRunnerOptions, type OllamaStreamingRunnerOptions, createOllamaRunner, createOllamaStreamingRunner };
|
package/dist/ollama.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
2
|
-
export{
|
|
1
|
+
var H=new Set(["http:","https:"]);function S(t){try{let e=new URL(t);if(!H.has(e.protocol))throw new Error(`[Directive] Invalid baseURL protocol "${e.protocol}" \u2013 only http: and https: are allowed`)}catch(e){throw e instanceof Error&&e.message.startsWith("[Directive]")?e:new Error(`[Directive] Invalid baseURL "${t}" \u2013 must be a valid URL (e.g. "https://api.openai.com/v1")`)}}function M(t){let{fetch:e=globalThis.fetch,buildRequest:n,parseResponse:o,parseOutput:u,hooks:f}=t,h=u??(l=>{try{return JSON.parse(l)}catch{return l}});return async(l,r,p)=>{let c=Date.now();f?.onBeforeCall?.({agent:l,input:r,timestamp:c});let s=[{role:"user",content:r}];try{let{url:a,init:d}=n(l,r,s),i=p?.signal?{...d,signal:p.signal}:d,m=await e(a,i);if(!m.ok){let y=await m.text().catch(()=>"");throw new Error(`[Directive] AgentRunner request failed: ${m.status} ${m.statusText}${y?` \u2013 ${y.slice(0,300)}`:""}`)}let g=await o(m,s),T={inputTokens:g.inputTokens??0,outputTokens:g.outputTokens??0},R={role:"assistant",content:g.text},w=[...s,R];p?.onMessage?.(R);let b=Date.now()-c;return f?.onAfterCall?.({agent:l,input:r,output:g.text,totalTokens:g.totalTokens,tokenUsage:T,durationMs:b,timestamp:Date.now()}),{output:h(g.text),messages:w,toolCalls:[],totalTokens:g.totalTokens,tokenUsage:T}}catch(a){let d=Date.now()-c;throw a instanceof Error&&f?.onError?.({agent:l,input:r,error:a,durationMs:d,timestamp:Date.now()}),a}}}function P(t,e,n){let o=Date.now();return t?.onBeforeCall?.({agent:e,input:n,timestamp:o}),o}function L(t,e,n,o,u,f,k){t?.onAfterCall?.({agent:e,input:n,output:o,totalTokens:u,tokenUsage:f,durationMs:Date.now()-k,timestamp:Date.now()});}function C(t,e,n,o,u){o instanceof Error&&t?.onError?.({agent:e,input:n,error:o,durationMs:Date.now()-u,timestamp:Date.now()});}function U(t,e,n,o){return {output:e,messages:[{role:"user",content:t},{role:"assistant",content:e}],toolCalls:[],totalTokens:n,tokenUsage:o}}var J={llama3:{input:0,output:0},"llama3.1":{input:0,output:0},"llama3.2":{input:0,output:0},"llama3.3":{input:0,output:0},mistral:{input:0,output:0},mixtral:{input:0,output:0},codellama:{input:0,output:0},gemma2:{input:0,output:0},phi3:{input:0,output:0},qwen2:{input:0,output:0},deepseek:{input:0,output:0},"deepseek-coder":{input:0,output:0},"command-r":{input:0,output:0}};function F(t={}){let{model:e="llama3",baseURL:n="http://localhost:11434",fetch:o=globalThis.fetch,timeoutMs:u,hooks:f,temperature:k,topP:h,stop:l,numPredict:r}=t;S(n);let p={};k!=null&&(p.temperature=k),h!=null&&(p.top_p=h),l!=null&&(p.stop=l),r!=null&&(p.num_predict=r);let c=Object.keys(p).length>0;return M({fetch:o,hooks:f,buildRequest:(s,a,d)=>({url:`${n}/api/chat`,init:{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:s.model??e,messages:[...s.instructions?[{role:"system",content:s.instructions}]:[],...d.map(i=>({role:i.role,content:i.content}))],stream:false,...c?{options:p}:{}}),...u!=null?{signal:AbortSignal.timeout(u)}:{}}}),parseResponse:async s=>{let a;try{a=await s.json();}catch{throw new Error(`[Directive] Ollama returned non-JSON response. Is Ollama running at ${n}? Start it with: ollama serve`)}let d=a.message?.content??"",i=a.prompt_eval_count??0,m=a.eval_count??0;return {text:d,totalTokens:i+m,inputTokens:i,outputTokens:m}}})}function V(t={}){let{model:e="llama3",baseURL:n="http://localhost:11434",fetch:o=globalThis.fetch,hooks:u,temperature:f,topP:k,stop:h,numPredict:l}=t;S(n);let r={};f!=null&&(r.temperature=f),k!=null&&(r.top_p=k),h!=null&&(r.stop=h),l!=null&&(r.num_predict=l);let p=Object.keys(r).length>0;return async(c,s,a)=>{let d=P(u,c,s);try{let i=await o(`${n}/api/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:c.model??e,messages:[...c.instructions?[{role:"system",content:c.instructions}]:[],{role:"user",content:s}],stream:!0,...p?{options:r}:{}}),signal:a.signal});if(!i.ok){let O=await i.text().catch(()=>"");throw new Error(`[Directive] Ollama streaming error ${i.status}${O?` \u2013 ${O.slice(0,200)}`:""}`)}let m=i.body?.getReader();if(!m)throw new Error("[Directive] No response body from Ollama");let g=new TextDecoder,T="",R="",w=0,b=0;try{for(;;){let{done:O,value:_}=await m.read();if(O)break;T+=g.decode(_,{stream:!0});let D=T.split(`
|
|
2
|
+
`);T=D.pop()??"";for(let $ of D){let x=$.trim();if(!x)continue;let v;try{v=JSON.parse(x);}catch{typeof process<"u"&&process.env?.NODE_ENV==="development"&&console.warn("[Directive] Malformed streaming chunk from Ollama:",x);continue}let A=v.message?.content??"";A&&(R+=A,a.onToken?.(A)),v.done&&(w=v.prompt_eval_count??0,b=v.eval_count??0);}}}finally{m.cancel().catch(()=>{});}let y={inputTokens:w,outputTokens:b},E=w+b;return a.onMessage?.({role:"assistant",content:R}),L(u,c,s,R,E,y,d),U(s,R,E,y)}catch(i){throw C(u,c,s,i,d),i}}}export{J as OLLAMA_PRICING,F as createOllamaRunner,V as createOllamaStreamingRunner};//# sourceMappingURL=ollama.js.map
|
|
3
3
|
//# sourceMappingURL=ollama.js.map
|
package/dist/ollama.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/agent-utils.ts","../src/adapters/ollama.ts"],"names":["ALLOWED_PROTOCOLS","validateBaseURL","baseURL","url","err","createRunner","options","fetchFn","buildRequest","parseResponse","parseOutput","hooks","parse","text","agent","input","runOptions","startTime","messages","init","fetchInit","response","errBody","parsed","tokenUsage","assistantMessage","allMessages","durationMs","createOllamaRunner","model","timeoutMs","_input","m","res","data","inputTokens","outputTokens"],"mappings":"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","file":"ollama.js","sourcesContent":["/**\n * Agent utilities — createRunner, estimateCost, state queries, URL validation.\n */\n\nimport type {\n AdapterHooks,\n AgentLike,\n AgentRunner,\n AgentState,\n ApprovalState,\n Message,\n RunOptions,\n RunResult,\n TokenUsage,\n} from \"./types.js\";\n\n// ============================================================================\n// State Query Helpers\n// ============================================================================\n\n/**\n * Check whether an agent is currently executing a run.\n *\n * @param state - The current {@link AgentState} to inspect.\n * @returns `true` when the agent status is `\"running\"`.\n */\nexport function isAgentRunning(state: AgentState): boolean {\n return state.status === \"running\";\n}\n\n/**\n * Check whether there are tool-call approvals waiting for user confirmation.\n *\n * @param state - The current {@link ApprovalState} to inspect.\n * @returns `true` when one or more approvals are pending.\n */\nexport function hasPendingApprovals(state: ApprovalState): boolean {\n return state.pending.length > 0;\n}\n\n// ============================================================================\n// Cost Estimation\n// ============================================================================\n\n/**\n * Estimate the dollar cost of an agent run based on total token usage.\n *\n * @remarks\n * No default rate is provided — callers must supply the current per-million-token\n * price to avoid silently using stale pricing.\n *\n * @param tokenUsage - Total number of tokens consumed (input + output).\n * @param ratePerMillionTokens - Cost in dollars per one million tokens.\n * @returns Estimated cost in dollars.\n */\nexport function estimateCost(\n tokenUsage: number,\n ratePerMillionTokens: number,\n): number {\n return (tokenUsage / 1_000_000) * ratePerMillionTokens;\n}\n\n// ============================================================================\n// Validation Helpers\n// ============================================================================\n\nconst ALLOWED_PROTOCOLS = new Set([\"http:\", \"https:\"]);\n\n/**\n * Validate that a base URL uses the `http:` or `https:` protocol.\n * Throws immediately at adapter creation time (not at call time) to surface\n * configuration errors before any LLM requests are made.\n *\n * @param baseURL - The base URL string to validate.\n * @throws When the URL is malformed or uses a protocol other than `http:` or `https:`.\n */\nexport function validateBaseURL(baseURL: string): void {\n try {\n const url = new URL(baseURL);\n if (!ALLOWED_PROTOCOLS.has(url.protocol)) {\n throw new Error(\n `[Directive] Invalid baseURL protocol \"${url.protocol}\" – only http: and https: are allowed`,\n );\n }\n } catch (err) {\n if (err instanceof Error && err.message.startsWith(\"[Directive]\")) {\n throw err;\n }\n\n throw new Error(\n `[Directive] Invalid baseURL \"${baseURL}\" – must be a valid URL (e.g. \"https://api.openai.com/v1\")`,\n );\n }\n}\n\n// ============================================================================\n// createRunner Helper\n// ============================================================================\n\n/** Parsed response from an LLM provider */\nexport interface ParsedResponse {\n text: string;\n totalTokens: number;\n /** Input token count, when available from the provider */\n inputTokens?: number;\n /** Output token count, when available from the provider */\n outputTokens?: number;\n}\n\n/** Options for creating an AgentRunner from buildRequest/parseResponse */\nexport interface CreateRunnerOptions {\n fetch?: typeof globalThis.fetch;\n buildRequest: (\n agent: AgentLike,\n input: string,\n messages: Message[],\n ) => { url: string; init: RequestInit };\n parseResponse: (\n response: Response,\n messages: Message[],\n ) => Promise<ParsedResponse>;\n parseOutput?: <T>(text: string) => T;\n /** Lifecycle hooks for tracing, logging, and metrics */\n hooks?: AdapterHooks;\n}\n\n/**\n * Create an {@link AgentRunner} from `buildRequest`/`parseResponse` helpers, reducing\n * ~50 lines of fetch boilerplate to ~20 lines of configuration.\n *\n * @remarks\n * Supports lifecycle hooks for observability:\n * - `onBeforeCall` fires before each API request\n * - `onAfterCall` fires after a successful response (includes token breakdown)\n * - `onError` fires when the request fails\n *\n * Output parsing defaults to `JSON.parse` with a string fallback. Supply a custom\n * `parseOutput` to override (e.g. for structured output schemas).\n *\n * @param options - Configuration for the runner, including request building, response parsing, and hooks.\n * @returns An {@link AgentRunner} function that performs LLM calls via fetch.\n *\n * @example\n * ```typescript\n * const runClaude = createRunner({\n * buildRequest: (agent, input) => ({\n * url: \"/api/claude\",\n * init: {\n * method: \"POST\",\n * headers: { \"Content-Type\": \"application/json\" },\n * body: JSON.stringify({\n * model: agent.model ?? \"claude-haiku-4-5-20251001\",\n * system: agent.instructions ?? \"\",\n * messages: [{ role: \"user\", content: input }],\n * }),\n * },\n * }),\n * parseResponse: async (res) => {\n * const data = await res.json();\n * const inputTokens = data.usage?.input_tokens ?? 0;\n * const outputTokens = data.usage?.output_tokens ?? 0;\n * return {\n * text: data.content?.[0]?.text ?? \"\",\n * totalTokens: inputTokens + outputTokens,\n * inputTokens,\n * outputTokens,\n * };\n * },\n * hooks: {\n * onAfterCall: ({ durationMs, tokenUsage }) => {\n * console.log(`LLM call: ${durationMs}ms, ${tokenUsage.inputTokens}in/${tokenUsage.outputTokens}out`);\n * },\n * },\n * });\n * ```\n *\n * @public\n */\nexport function createRunner(options: CreateRunnerOptions): AgentRunner {\n const {\n fetch: fetchFn = globalThis.fetch,\n buildRequest,\n parseResponse,\n parseOutput,\n hooks,\n } = options;\n\n const defaultParseOutput = <T>(text: string): T => {\n try {\n return JSON.parse(text) as T;\n } catch {\n return text as unknown as T;\n }\n };\n\n const parse = parseOutput ?? defaultParseOutput;\n\n return async <T = unknown>(\n agent: AgentLike,\n input: string,\n runOptions?: RunOptions,\n ): Promise<RunResult<T>> => {\n const startTime = Date.now();\n hooks?.onBeforeCall?.({ agent, input, timestamp: startTime });\n\n const messages: Message[] = [{ role: \"user\", content: input }];\n\n try {\n const { url, init } = buildRequest(agent, input, messages);\n\n const fetchInit: RequestInit = runOptions?.signal\n ? { ...init, signal: runOptions.signal }\n : init;\n\n const response = await fetchFn(url, fetchInit);\n\n if (!response.ok) {\n const errBody = await response.text().catch(() => \"\");\n\n throw new Error(\n `[Directive] AgentRunner request failed: ${response.status} ${response.statusText}${errBody ? ` – ${errBody.slice(0, 300)}` : \"\"}`,\n );\n }\n\n const parsed = await parseResponse(response, messages);\n const tokenUsage: TokenUsage = {\n inputTokens: parsed.inputTokens ?? 0,\n outputTokens: parsed.outputTokens ?? 0,\n };\n\n const assistantMessage: Message = {\n role: \"assistant\",\n content: parsed.text,\n };\n const allMessages: Message[] = [...messages, assistantMessage];\n\n runOptions?.onMessage?.(assistantMessage);\n\n const durationMs = Date.now() - startTime;\n hooks?.onAfterCall?.({\n agent,\n input,\n output: parsed.text,\n totalTokens: parsed.totalTokens,\n tokenUsage,\n durationMs,\n timestamp: Date.now(),\n });\n\n return {\n output: parse<T>(parsed.text),\n messages: allMessages,\n toolCalls: [],\n totalTokens: parsed.totalTokens,\n tokenUsage,\n };\n } catch (err) {\n const durationMs = Date.now() - startTime;\n if (err instanceof Error) {\n hooks?.onError?.({\n agent,\n input,\n error: err,\n durationMs,\n timestamp: Date.now(),\n });\n }\n\n throw err;\n }\n };\n}\n","/**\n * @directive-run/ai/ollama\n *\n * Ollama adapter for Directive AI. Provides runners for local\n * Ollama inference. No API key required.\n *\n * Requires Ollama to be running locally. Start it with: `ollama serve`\n *\n * @example\n * ```typescript\n * import { createOllamaRunner } from '@directive-run/ai/ollama';\n *\n * const runner = createOllamaRunner({ model: 'llama3' });\n * ```\n */\n\nimport { createRunner, validateBaseURL } from \"../agent-utils.js\";\nimport type { AdapterHooks, AgentRunner } from \"../types.js\";\n\n// ============================================================================\n// Ollama Runner\n// ============================================================================\n\n/** Options for createOllamaRunner */\nexport interface OllamaRunnerOptions {\n model?: string;\n baseURL?: string;\n fetch?: typeof globalThis.fetch;\n /** @default undefined */\n timeoutMs?: number;\n /** Lifecycle hooks for tracing, logging, and metrics */\n hooks?: AdapterHooks;\n}\n\n/**\n * Create an AgentRunner for local Ollama inference.\n *\n * Ollama runs locally – no API key or cloud service needed. Default model\n * is `llama3`, default base URL is `http://localhost:11434`.\n *\n * Returns `tokenUsage` with input/output breakdown for cost tracking\n * (useful for monitoring local resource usage).\n *\n * @example\n * ```typescript\n * const runner = createOllamaRunner({ model: \"llama3\" });\n * const orchestrator = createAgentOrchestrator({ runner });\n * const result = await orchestrator.run(agent, input);\n * ```\n */\nexport function createOllamaRunner(\n options: OllamaRunnerOptions = {},\n): AgentRunner {\n const {\n model = \"llama3\",\n baseURL = \"http://localhost:11434\",\n fetch: fetchFn = globalThis.fetch,\n timeoutMs,\n hooks,\n } = options;\n\n validateBaseURL(baseURL);\n\n return createRunner({\n fetch: fetchFn,\n hooks,\n buildRequest: (agent, _input, messages) => ({\n url: `${baseURL}/api/chat`,\n init: {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n model: agent.model ?? model,\n messages: [\n ...(agent.instructions\n ? [{ role: \"system\", content: agent.instructions }]\n : []),\n ...messages.map((m) => ({ role: m.role, content: m.content })),\n ],\n stream: false,\n }),\n ...(timeoutMs != null\n ? { signal: AbortSignal.timeout(timeoutMs) }\n : {}),\n },\n }),\n parseResponse: async (res) => {\n let data: Record<string, unknown>;\n try {\n data = await res.json();\n } catch {\n throw new Error(\n `[Directive] Ollama returned non-JSON response. Is Ollama running at ${baseURL}? Start it with: ollama serve`,\n );\n }\n const text =\n ((data.message as Record<string, unknown>)?.content as string) ?? \"\";\n const inputTokens = (data.prompt_eval_count as number) ?? 0;\n const outputTokens = (data.eval_count as number) ?? 0;\n\n return {\n text,\n totalTokens: inputTokens + outputTokens,\n inputTokens,\n outputTokens,\n };\n },\n });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/agent-utils.ts","../src/adapters/shared.ts","../src/adapters/ollama.ts"],"names":["ALLOWED_PROTOCOLS","validateBaseURL","baseURL","url","err","createRunner","options","fetchFn","buildRequest","parseResponse","parseOutput","hooks","parse","text","agent","input","runOptions","startTime","messages","init","fetchInit","response","errBody","parsed","tokenUsage","assistantMessage","allMessages","durationMs","fireBeforeCallHook","fireAfterCallHook","output","totalTokens","fireErrorHook","buildStreamingResult","fullText","OLLAMA_PRICING","createOllamaRunner","model","timeoutMs","temperature","topP","stop","numPredict","ollamaOptions","hasOptions","_input","m","res","data","inputTokens","outputTokens","createOllamaStreamingRunner","callbacks","reader","decoder","buf","done","value","lines","line","trimmed","chunk","content"],"mappings":"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","file":"ollama.js","sourcesContent":["/**\n * Agent utilities — createRunner, estimateCost, state queries, URL validation.\n */\n\nimport type {\n AdapterHooks,\n AgentLike,\n AgentRunner,\n AgentState,\n ApprovalState,\n Message,\n RunOptions,\n RunResult,\n TokenUsage,\n} from \"./types.js\";\n\n// ============================================================================\n// State Query Helpers\n// ============================================================================\n\n/**\n * Check whether an agent is currently executing a run.\n *\n * @param state - The current {@link AgentState} to inspect.\n * @returns `true` when the agent status is `\"running\"`.\n */\nexport function isAgentRunning(state: AgentState): boolean {\n return state.status === \"running\";\n}\n\n/**\n * Check whether there are tool-call approvals waiting for user confirmation.\n *\n * @param state - The current {@link ApprovalState} to inspect.\n * @returns `true` when one or more approvals are pending.\n */\nexport function hasPendingApprovals(state: ApprovalState): boolean {\n return state.pending.length > 0;\n}\n\n// ============================================================================\n// Cost Estimation\n// ============================================================================\n\n/**\n * Estimate the dollar cost of an agent run based on total token usage.\n *\n * @remarks\n * No default rate is provided — callers must supply the current per-million-token\n * price to avoid silently using stale pricing.\n *\n * @param tokenUsage - Total number of tokens consumed (input + output).\n * @param ratePerMillionTokens - Cost in dollars per one million tokens.\n * @returns Estimated cost in dollars.\n */\nexport function estimateCost(\n tokenUsage: number,\n ratePerMillionTokens: number,\n): number {\n return (tokenUsage / 1_000_000) * ratePerMillionTokens;\n}\n\n// ============================================================================\n// Validation Helpers\n// ============================================================================\n\nconst ALLOWED_PROTOCOLS = new Set([\"http:\", \"https:\"]);\n\n/**\n * Validate that a base URL uses the `http:` or `https:` protocol.\n * Throws immediately at adapter creation time (not at call time) to surface\n * configuration errors before any LLM requests are made.\n *\n * @param baseURL - The base URL string to validate.\n * @throws When the URL is malformed or uses a protocol other than `http:` or `https:`.\n */\nexport function validateBaseURL(baseURL: string): void {\n try {\n const url = new URL(baseURL);\n if (!ALLOWED_PROTOCOLS.has(url.protocol)) {\n throw new Error(\n `[Directive] Invalid baseURL protocol \"${url.protocol}\" – only http: and https: are allowed`,\n );\n }\n } catch (err) {\n if (err instanceof Error && err.message.startsWith(\"[Directive]\")) {\n throw err;\n }\n\n throw new Error(\n `[Directive] Invalid baseURL \"${baseURL}\" – must be a valid URL (e.g. \"https://api.openai.com/v1\")`,\n );\n }\n}\n\n// ============================================================================\n// createRunner Helper\n// ============================================================================\n\n/** Parsed response from an LLM provider */\nexport interface ParsedResponse {\n text: string;\n totalTokens: number;\n /** Input token count, when available from the provider */\n inputTokens?: number;\n /** Output token count, when available from the provider */\n outputTokens?: number;\n}\n\n/** Options for creating an AgentRunner from buildRequest/parseResponse */\nexport interface CreateRunnerOptions {\n fetch?: typeof globalThis.fetch;\n buildRequest: (\n agent: AgentLike,\n input: string,\n messages: Message[],\n ) => { url: string; init: RequestInit };\n parseResponse: (\n response: Response,\n messages: Message[],\n ) => Promise<ParsedResponse>;\n parseOutput?: <T>(text: string) => T;\n /** Lifecycle hooks for tracing, logging, and metrics */\n hooks?: AdapterHooks;\n}\n\n/**\n * Create an {@link AgentRunner} from `buildRequest`/`parseResponse` helpers, reducing\n * ~50 lines of fetch boilerplate to ~20 lines of configuration.\n *\n * @remarks\n * Supports lifecycle hooks for observability:\n * - `onBeforeCall` fires before each API request\n * - `onAfterCall` fires after a successful response (includes token breakdown)\n * - `onError` fires when the request fails\n *\n * Output parsing defaults to `JSON.parse` with a string fallback. Supply a custom\n * `parseOutput` to override (e.g. for structured output schemas).\n *\n * @param options - Configuration for the runner, including request building, response parsing, and hooks.\n * @returns An {@link AgentRunner} function that performs LLM calls via fetch.\n *\n * @example\n * ```typescript\n * const runClaude = createRunner({\n * buildRequest: (agent, input) => ({\n * url: \"/api/claude\",\n * init: {\n * method: \"POST\",\n * headers: { \"Content-Type\": \"application/json\" },\n * body: JSON.stringify({\n * model: agent.model ?? \"claude-haiku-4-5-20251001\",\n * system: agent.instructions ?? \"\",\n * messages: [{ role: \"user\", content: input }],\n * }),\n * },\n * }),\n * parseResponse: async (res) => {\n * const data = await res.json();\n * const inputTokens = data.usage?.input_tokens ?? 0;\n * const outputTokens = data.usage?.output_tokens ?? 0;\n * return {\n * text: data.content?.[0]?.text ?? \"\",\n * totalTokens: inputTokens + outputTokens,\n * inputTokens,\n * outputTokens,\n * };\n * },\n * hooks: {\n * onAfterCall: ({ durationMs, tokenUsage }) => {\n * console.log(`LLM call: ${durationMs}ms, ${tokenUsage.inputTokens}in/${tokenUsage.outputTokens}out`);\n * },\n * },\n * });\n * ```\n *\n * @public\n */\nexport function createRunner(options: CreateRunnerOptions): AgentRunner {\n const {\n fetch: fetchFn = globalThis.fetch,\n buildRequest,\n parseResponse,\n parseOutput,\n hooks,\n } = options;\n\n const defaultParseOutput = <T>(text: string): T => {\n try {\n return JSON.parse(text) as T;\n } catch {\n return text as unknown as T;\n }\n };\n\n const parse = parseOutput ?? defaultParseOutput;\n\n return async <T = unknown>(\n agent: AgentLike,\n input: string,\n runOptions?: RunOptions,\n ): Promise<RunResult<T>> => {\n const startTime = Date.now();\n hooks?.onBeforeCall?.({ agent, input, timestamp: startTime });\n\n const messages: Message[] = [{ role: \"user\", content: input }];\n\n try {\n const { url, init } = buildRequest(agent, input, messages);\n\n const fetchInit: RequestInit = runOptions?.signal\n ? { ...init, signal: runOptions.signal }\n : init;\n\n const response = await fetchFn(url, fetchInit);\n\n if (!response.ok) {\n const errBody = await response.text().catch(() => \"\");\n\n throw new Error(\n `[Directive] AgentRunner request failed: ${response.status} ${response.statusText}${errBody ? ` – ${errBody.slice(0, 300)}` : \"\"}`,\n );\n }\n\n const parsed = await parseResponse(response, messages);\n const tokenUsage: TokenUsage = {\n inputTokens: parsed.inputTokens ?? 0,\n outputTokens: parsed.outputTokens ?? 0,\n };\n\n const assistantMessage: Message = {\n role: \"assistant\",\n content: parsed.text,\n };\n const allMessages: Message[] = [...messages, assistantMessage];\n\n runOptions?.onMessage?.(assistantMessage);\n\n const durationMs = Date.now() - startTime;\n hooks?.onAfterCall?.({\n agent,\n input,\n output: parsed.text,\n totalTokens: parsed.totalTokens,\n tokenUsage,\n durationMs,\n timestamp: Date.now(),\n });\n\n return {\n output: parse<T>(parsed.text),\n messages: allMessages,\n toolCalls: [],\n totalTokens: parsed.totalTokens,\n tokenUsage,\n };\n } catch (err) {\n const durationMs = Date.now() - startTime;\n if (err instanceof Error) {\n hooks?.onError?.({\n agent,\n input,\n error: err,\n durationMs,\n timestamp: Date.now(),\n });\n }\n\n throw err;\n }\n };\n}\n","/**\n * Shared utilities for streaming adapters.\n *\n * Extracts common SSE parsing, error handling, hook lifecycle, and response\n * building logic used across Anthropic, OpenAI, and Gemini streaming runners.\n */\n\nimport type {\n AdapterHooks,\n AgentLike,\n Message,\n TokenUsage,\n} from \"../types.js\";\n\n// ============================================================================\n// HTTP Error Handling\n// ============================================================================\n\n/**\n * Throw a standardized HTTP error from a streaming response.\n * Reads up to 200 chars of the error body for diagnostics.\n */\nexport async function throwStreamingHTTPError(\n response: Response,\n adapterName: string,\n): Promise<never> {\n const errBody = await response.text().catch(() => \"\");\n\n throw new Error(\n `[Directive] ${adapterName} streaming error ${response.status}${errBody ? ` – ${errBody.slice(0, 200)}` : \"\"}`,\n );\n}\n\n/**\n * Get an SSE reader from a response, throwing if body is missing.\n */\nexport function getSSEReader(\n response: Response,\n): ReadableStreamDefaultReader<Uint8Array> {\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error(\"[Directive] No response body\");\n }\n\n return reader;\n}\n\n// ============================================================================\n// API Key Validation\n// ============================================================================\n\n/**\n * Warn in non-production environments if an API key is empty.\n */\nexport function warnIfMissingApiKey(\n apiKey: string | undefined,\n functionName: string,\n): void {\n if (\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV !== \"production\" &&\n !apiKey\n ) {\n console.warn(\n `[Directive] ${functionName}: apiKey is empty. API calls will fail.`,\n );\n }\n}\n\n// ============================================================================\n// SSE Stream Parser\n// ============================================================================\n\n/** Result from parsing a single SSE event (provider-specific). */\nexport interface SSEEventResult {\n /** Text token to append to output. */\n text?: string;\n /** Updated input token count (cumulative, not delta). */\n inputTokens?: number;\n /** Updated output token count (cumulative, not delta). */\n outputTokens?: number;\n}\n\n/**\n * Parse an SSE stream from a Response, calling `onToken` for each text chunk\n * and `parseEvent` for provider-specific event extraction.\n *\n * Handles buffering, `[DONE]` sentinels, malformed JSON, and reader cleanup.\n *\n * @param reader - The ReadableStream reader from the response body.\n * @param onToken - Callback for each text token (may be undefined).\n * @param parseEvent - Provider-specific function to extract text and tokens from a parsed SSE event.\n * @param adapterName - Adapter name for dev-mode warnings.\n * @returns The full text output and final token counts.\n */\nexport async function parseSSEStream(\n reader: ReadableStreamDefaultReader<Uint8Array>,\n onToken: ((token: string) => void) | undefined,\n parseEvent: (event: Record<string, unknown>) => SSEEventResult,\n adapterName: string,\n): Promise<{ fullText: string; inputTokens: number; outputTokens: number }> {\n const decoder = new TextDecoder();\n let buf = \"\";\n let fullText = \"\";\n let inputTokens = 0;\n let outputTokens = 0;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n buf += decoder.decode(value, { stream: true });\n const lines = buf.split(\"\\n\");\n buf = lines.pop() ?? \"\";\n\n for (const line of lines) {\n if (!line.startsWith(\"data: \")) {\n continue;\n }\n const data = line.slice(6).trim();\n if (data === \"[DONE]\") {\n continue;\n }\n\n try {\n const event = JSON.parse(data);\n const result = parseEvent(event);\n if (result.text) {\n fullText += result.text;\n onToken?.(result.text);\n }\n if (result.inputTokens !== undefined) {\n inputTokens = result.inputTokens;\n }\n if (result.outputTokens !== undefined) {\n outputTokens = result.outputTokens;\n }\n } catch (parseErr) {\n if (parseErr instanceof SyntaxError) {\n if (\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV === \"development\"\n ) {\n console.warn(\n `[Directive] Malformed SSE event from ${adapterName}:`,\n data,\n );\n }\n } else {\n throw parseErr;\n }\n }\n }\n }\n } finally {\n reader.cancel().catch(() => {});\n }\n\n return { fullText, inputTokens, outputTokens };\n}\n\n// ============================================================================\n// Hook Lifecycle Helpers\n// ============================================================================\n\n/**\n * Fire the onBeforeCall hook and return the start timestamp.\n */\nexport function fireBeforeCallHook(\n hooks: AdapterHooks | undefined,\n agent: AgentLike,\n input: string,\n): number {\n const startTime = Date.now();\n hooks?.onBeforeCall?.({ agent, input, timestamp: startTime });\n\n return startTime;\n}\n\n/**\n * Fire the onAfterCall hook with timing and token data.\n */\nexport function fireAfterCallHook(\n hooks: AdapterHooks | undefined,\n agent: AgentLike,\n input: string,\n output: string,\n totalTokens: number,\n tokenUsage: TokenUsage,\n startTime: number,\n): void {\n hooks?.onAfterCall?.({\n agent,\n input,\n output,\n totalTokens,\n tokenUsage,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n });\n}\n\n/**\n * Fire the onError hook if the error is an Error instance.\n */\nexport function fireErrorHook(\n hooks: AdapterHooks | undefined,\n agent: AgentLike,\n input: string,\n err: unknown,\n startTime: number,\n): void {\n if (err instanceof Error) {\n hooks?.onError?.({\n agent,\n input,\n error: err,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n });\n }\n}\n\n// ============================================================================\n// Streaming Response Builder\n// ============================================================================\n\n/**\n * Build the standard streaming runner return value.\n */\nexport function buildStreamingResult(\n input: string,\n fullText: string,\n totalTokens: number,\n tokenUsage: TokenUsage,\n): {\n output: string;\n messages: Message[];\n toolCalls: never[];\n totalTokens: number;\n tokenUsage: TokenUsage;\n} {\n const assistantMsg: Message = { role: \"assistant\", content: fullText };\n\n return {\n output: fullText,\n messages: [{ role: \"user\" as const, content: input }, assistantMsg],\n toolCalls: [],\n totalTokens,\n tokenUsage,\n };\n}\n","/**\n * @directive-run/ai/ollama\n *\n * Ollama adapter for Directive AI. Provides runners for local\n * Ollama inference. No API key required.\n *\n * Requires Ollama to be running locally. Start it with: `ollama serve`\n *\n * @example\n * ```typescript\n * import { createOllamaRunner } from '@directive-run/ai/ollama';\n *\n * const runner = createOllamaRunner({ model: 'llama3' });\n * ```\n */\n\nimport { createRunner, validateBaseURL } from \"../agent-utils.js\";\nimport type {\n AdapterHooks,\n AgentRunner,\n} from \"../types.js\";\nimport type { StreamingCallbackRunner } from \"../types.js\";\nimport {\n buildStreamingResult,\n fireAfterCallHook,\n fireBeforeCallHook,\n fireErrorHook,\n} from \"./shared.js\";\n\n// ============================================================================\n// Pricing Constants\n// ============================================================================\n\n/**\n * Ollama model pricing (USD per million tokens).\n *\n * Ollama runs locally — all costs are zero. This table is provided for\n * compatibility with `estimateCost()` so local models integrate seamlessly\n * into cost-tracking pipelines.\n *\n * @example\n * ```typescript\n * import { estimateCost } from '@directive-run/ai';\n * import { OLLAMA_PRICING } from '@directive-run/ai/ollama';\n *\n * const cost =\n * estimateCost(result.tokenUsage!.inputTokens, OLLAMA_PRICING[\"llama3\"].input) +\n * estimateCost(result.tokenUsage!.outputTokens, OLLAMA_PRICING[\"llama3\"].output);\n * // → 0\n * ```\n */\nexport const OLLAMA_PRICING: Record<string, { input: number; output: number }> =\n {\n llama3: { input: 0, output: 0 },\n \"llama3.1\": { input: 0, output: 0 },\n \"llama3.2\": { input: 0, output: 0 },\n \"llama3.3\": { input: 0, output: 0 },\n mistral: { input: 0, output: 0 },\n mixtral: { input: 0, output: 0 },\n codellama: { input: 0, output: 0 },\n \"gemma2\": { input: 0, output: 0 },\n phi3: { input: 0, output: 0 },\n qwen2: { input: 0, output: 0 },\n deepseek: { input: 0, output: 0 },\n \"deepseek-coder\": { input: 0, output: 0 },\n \"command-r\": { input: 0, output: 0 },\n };\n\n// ============================================================================\n// Ollama Runner\n// ============================================================================\n\n/** Options for createOllamaRunner */\nexport interface OllamaRunnerOptions {\n model?: string;\n baseURL?: string;\n fetch?: typeof globalThis.fetch;\n /** @default undefined */\n timeoutMs?: number;\n /** Lifecycle hooks for tracing, logging, and metrics */\n hooks?: AdapterHooks;\n /** Sampling temperature. Higher = more random. */\n temperature?: number;\n /** Nucleus sampling: top-P probability mass (0–1). */\n topP?: number;\n /** Stop sequences. The model will stop generating when it encounters one. */\n stop?: string[];\n /** Maximum number of tokens to generate. Ollama uses `num_predict`. */\n numPredict?: number;\n}\n\n/**\n * Create an AgentRunner for local Ollama inference.\n *\n * Ollama runs locally – no API key or cloud service needed. Default model\n * is `llama3`, default base URL is `http://localhost:11434`.\n *\n * Returns `tokenUsage` with input/output breakdown for cost tracking\n * (useful for monitoring local resource usage).\n *\n * @example\n * ```typescript\n * const runner = createOllamaRunner({ model: \"llama3\" });\n * const orchestrator = createAgentOrchestrator({ runner });\n * const result = await orchestrator.run(agent, input);\n * ```\n */\nexport function createOllamaRunner(\n options: OllamaRunnerOptions = {},\n): AgentRunner {\n const {\n model = \"llama3\",\n baseURL = \"http://localhost:11434\",\n fetch: fetchFn = globalThis.fetch,\n timeoutMs,\n hooks,\n temperature,\n topP,\n stop,\n numPredict,\n } = options;\n\n validateBaseURL(baseURL);\n\n const ollamaOptions: Record<string, unknown> = {};\n if (temperature != null) {\n ollamaOptions.temperature = temperature;\n }\n if (topP != null) {\n ollamaOptions.top_p = topP;\n }\n if (stop != null) {\n ollamaOptions.stop = stop;\n }\n if (numPredict != null) {\n ollamaOptions.num_predict = numPredict;\n }\n const hasOptions = Object.keys(ollamaOptions).length > 0;\n\n return createRunner({\n fetch: fetchFn,\n hooks,\n buildRequest: (agent, _input, messages) => ({\n url: `${baseURL}/api/chat`,\n init: {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n model: agent.model ?? model,\n messages: [\n ...(agent.instructions\n ? [{ role: \"system\", content: agent.instructions }]\n : []),\n ...messages.map((m) => ({ role: m.role, content: m.content })),\n ],\n stream: false,\n ...(hasOptions ? { options: ollamaOptions } : {}),\n }),\n ...(timeoutMs != null\n ? { signal: AbortSignal.timeout(timeoutMs) }\n : {}),\n },\n }),\n parseResponse: async (res) => {\n let data: Record<string, unknown>;\n try {\n data = await res.json();\n } catch {\n throw new Error(\n `[Directive] Ollama returned non-JSON response. Is Ollama running at ${baseURL}? Start it with: ollama serve`,\n );\n }\n const text =\n ((data.message as Record<string, unknown>)?.content as string) ?? \"\";\n const inputTokens = (data.prompt_eval_count as number) ?? 0;\n const outputTokens = (data.eval_count as number) ?? 0;\n\n return {\n text,\n totalTokens: inputTokens + outputTokens,\n inputTokens,\n outputTokens,\n };\n },\n });\n}\n\n// ============================================================================\n// Ollama Streaming Runner\n// ============================================================================\n\n/** Options for createOllamaStreamingRunner */\nexport interface OllamaStreamingRunnerOptions {\n model?: string;\n baseURL?: string;\n fetch?: typeof globalThis.fetch;\n /** Lifecycle hooks for tracing, logging, and metrics */\n hooks?: AdapterHooks;\n /** Sampling temperature. Higher = more random. */\n temperature?: number;\n /** Nucleus sampling: top-P probability mass (0–1). */\n topP?: number;\n /** Stop sequences. The model will stop generating when it encounters one. */\n stop?: string[];\n /** Maximum number of tokens to generate. Ollama uses `num_predict`. */\n numPredict?: number;\n}\n\n/**\n * Create a StreamingCallbackRunner for local Ollama inference with\n * chunked JSON streaming. Can be used standalone or paired with `createOllamaRunner`.\n *\n * Ollama streams newline-delimited JSON objects with `{ message: { content }, done }`.\n * Token counts are included in the final chunk (`prompt_eval_count`, `eval_count`).\n *\n * Returns `tokenUsage` with input/output breakdown for resource tracking.\n *\n * @example\n * ```typescript\n * const streamingRunner = createOllamaStreamingRunner({ model: 'llama3' });\n * const streamRunner = createStreamingRunner(streamingRunner);\n * const { stream, result } = streamRunner(agent, input);\n * ```\n */\nexport function createOllamaStreamingRunner(\n options: OllamaStreamingRunnerOptions = {},\n): StreamingCallbackRunner {\n const {\n model = \"llama3\",\n baseURL = \"http://localhost:11434\",\n fetch: fetchFn = globalThis.fetch,\n hooks,\n temperature,\n topP,\n stop,\n numPredict,\n } = options;\n\n validateBaseURL(baseURL);\n\n const ollamaOptions: Record<string, unknown> = {};\n if (temperature != null) {\n ollamaOptions.temperature = temperature;\n }\n if (topP != null) {\n ollamaOptions.top_p = topP;\n }\n if (stop != null) {\n ollamaOptions.stop = stop;\n }\n if (numPredict != null) {\n ollamaOptions.num_predict = numPredict;\n }\n const hasOptions = Object.keys(ollamaOptions).length > 0;\n\n return async (agent, input, callbacks) => {\n const startTime = fireBeforeCallHook(hooks, agent, input);\n\n try {\n const response = await fetchFn(`${baseURL}/api/chat`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n model: agent.model ?? model,\n messages: [\n ...(agent.instructions\n ? [{ role: \"system\", content: agent.instructions }]\n : []),\n { role: \"user\", content: input },\n ],\n stream: true,\n ...(hasOptions ? { options: ollamaOptions } : {}),\n }),\n signal: callbacks.signal,\n });\n\n if (!response.ok) {\n const errBody = await response.text().catch(() => \"\");\n\n throw new Error(\n `[Directive] Ollama streaming error ${response.status}${errBody ? ` – ${errBody.slice(0, 200)}` : \"\"}`,\n );\n }\n\n const reader = response.body?.getReader();\n if (!reader) {\n throw new Error(\"[Directive] No response body from Ollama\");\n }\n\n const decoder = new TextDecoder();\n let buf = \"\";\n let fullText = \"\";\n let inputTokens = 0;\n let outputTokens = 0;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n buf += decoder.decode(value, { stream: true });\n const lines = buf.split(\"\\n\");\n buf = lines.pop() ?? \"\";\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) {\n continue;\n }\n\n let chunk: Record<string, unknown>;\n try {\n chunk = JSON.parse(trimmed);\n } catch {\n if (\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV === \"development\"\n ) {\n console.warn(\n \"[Directive] Malformed streaming chunk from Ollama:\",\n trimmed,\n );\n }\n\n continue;\n }\n\n const msg = chunk.message as\n | Record<string, unknown>\n | undefined;\n const content = (msg?.content as string) ?? \"\";\n if (content) {\n fullText += content;\n callbacks.onToken?.(content);\n }\n\n if (chunk.done) {\n inputTokens =\n (chunk.prompt_eval_count as number) ?? 0;\n outputTokens = (chunk.eval_count as number) ?? 0;\n }\n }\n }\n } finally {\n reader.cancel().catch(() => {});\n }\n\n const tokenUsage = { inputTokens, outputTokens };\n const totalTokens = inputTokens + outputTokens;\n\n callbacks.onMessage?.({ role: \"assistant\", content: fullText });\n fireAfterCallHook(hooks, agent, input, fullText, totalTokens, tokenUsage, startTime);\n\n return buildStreamingResult(input, fullText, totalTokens, tokenUsage);\n } catch (err) {\n fireErrorHook(hooks, agent, input, err, startTime);\n\n throw err;\n }\n };\n}\n"]}
|
package/dist/openai.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
'use strict';var C=new Set(["http:","https:"]);function
|
|
2
|
-
`);
|
|
3
|
-
exports.OPENAI_PRICING=
|
|
1
|
+
'use strict';var C=new Set(["http:","https:"]);function S(t){try{let e=new URL(t);if(!C.has(e.protocol))throw new Error(`[Directive] Invalid baseURL protocol "${e.protocol}" \u2013 only http: and https: are allowed`)}catch(e){throw e instanceof Error&&e.message.startsWith("[Directive]")?e:new Error(`[Directive] Invalid baseURL "${t}" \u2013 must be a valid URL (e.g. "https://api.openai.com/v1")`)}}function v(t){let{fetch:e=globalThis.fetch,buildRequest:r,parseResponse:n,parseOutput:u,hooks:l}=t,h=u??(o=>{try{return JSON.parse(o)}catch{return o}});return async(o,g,f)=>{let m=Date.now();l?.onBeforeCall?.({agent:o,input:g,timestamp:m});let p=[{role:"user",content:g}];try{let{url:s,init:a}=r(o,g,p),c=f?.signal?{...a,signal:f.signal}:a,i=await e(s,c);if(!i.ok){let R=await i.text().catch(()=>"");throw new Error(`[Directive] AgentRunner request failed: ${i.status} ${i.statusText}${R?` \u2013 ${R.slice(0,300)}`:""}`)}let k=await n(i,p),b={inputTokens:k.inputTokens??0,outputTokens:k.outputTokens??0},T={role:"assistant",content:k.text},y=[...p,T];f?.onMessage?.(T);let w=Date.now()-m;return l?.onAfterCall?.({agent:o,input:g,output:k.text,totalTokens:k.totalTokens,tokenUsage:b,durationMs:w,timestamp:Date.now()}),{output:h(k.text),messages:y,toolCalls:[],totalTokens:k.totalTokens,tokenUsage:b}}catch(s){let a=Date.now()-m;throw s instanceof Error&&l?.onError?.({agent:o,input:g,error:s,durationMs:a,timestamp:Date.now()}),s}}}async function D(t,e){let r=await t.text().catch(()=>"");throw new Error(`[Directive] ${e} streaming error ${t.status}${r?` \u2013 ${r.slice(0,200)}`:""}`)}function I(t){let e=t.body?.getReader();if(!e)throw new Error("[Directive] No response body");return e}function O(t,e){typeof process<"u"&&process.env?.NODE_ENV!=="production"&&!t&&console.warn(`[Directive] ${e}: apiKey is empty. API calls will fail.`);}async function M(t,e,r,n){let u=new TextDecoder,l="",d="",h=0,o=0;try{for(;;){let{done:g,value:f}=await t.read();if(g)break;l+=u.decode(f,{stream:!0});let m=l.split(`
|
|
2
|
+
`);l=m.pop()??"";for(let p of m){if(!p.startsWith("data: "))continue;let s=p.slice(6).trim();if(s!=="[DONE]")try{let a=JSON.parse(s),c=r(a);c.text&&(d+=c.text,e?.(c.text)),c.inputTokens!==void 0&&(h=c.inputTokens),c.outputTokens!==void 0&&(o=c.outputTokens);}catch(a){if(a instanceof SyntaxError)typeof process<"u"&&process.env?.NODE_ENV==="development"&&console.warn(`[Directive] Malformed SSE event from ${n}:`,s);else throw a}}}}finally{t.cancel().catch(()=>{});}return {fullText:d,inputTokens:h,outputTokens:o}}function _(t,e,r){let n=Date.now();return t?.onBeforeCall?.({agent:e,input:r,timestamp:n}),n}function P(t,e,r,n,u,l,d){t?.onAfterCall?.({agent:e,input:r,output:n,totalTokens:u,tokenUsage:l,durationMs:Date.now()-d,timestamp:Date.now()});}function j(t,e,r,n,u){n instanceof Error&&t?.onError?.({agent:e,input:r,error:n,durationMs:Date.now()-u,timestamp:Date.now()});}function U(t,e,r,n){return {output:e,messages:[{role:"user",content:t},{role:"assistant",content:e}],toolCalls:[],totalTokens:r,tokenUsage:n}}var B={"gpt-4.1":{input:2,output:8},"gpt-4.1-mini":{input:.4,output:1.6},"gpt-4.1-nano":{input:.1,output:.4},"gpt-4o":{input:2.5,output:10},"gpt-4o-mini":{input:.15,output:.6},"gpt-4-turbo":{input:10,output:30},"o4-mini":{input:1.1,output:4.4},o3:{input:10,output:40},"o3-mini":{input:1.1,output:4.4}};function F(t){let{apiKey:e,model:r="gpt-4o",maxTokens:n,baseURL:u="https://api.openai.com/v1",fetch:l=globalThis.fetch,timeoutMs:d,hooks:h,temperature:o,topP:g,stop:f,responseFormat:m}=t;S(u),O(e,"createOpenAIRunner");let p=m==="json"?{type:"json_object"}:m??void 0;return v({fetch:l,hooks:h,buildRequest:(s,a,c)=>({url:`${u}/chat/completions`,init:{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify({model:s.model??r,...n!=null?{max_tokens:n}:{},...o!=null?{temperature:o}:{},...g!=null?{top_p:g}:{},...f!=null?{stop:f}:{},...p!=null?{response_format:p}:{},messages:[...s.instructions?[{role:"system",content:s.instructions}]:[],...c.map(i=>({role:i.role,content:i.content}))]}),...d!=null?{signal:AbortSignal.timeout(d)}:{}}}),parseResponse:async s=>{let a=await s.json(),c=a.choices?.[0]?.message?.content??"",i=a.usage?.prompt_tokens??0,k=a.usage?.completion_tokens??0;return {text:c,totalTokens:i+k,inputTokens:i,outputTokens:k}}})}function K(t){let{apiKey:e,model:r="text-embedding-3-small",dimensions:n=1536,baseURL:u="https://api.openai.com/v1",fetch:l=globalThis.fetch,timeoutMs:d}=t;return S(u),O(e,"createOpenAIEmbedder"),async h=>{let o=await l(`${u}/embeddings`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify({model:r,input:h,dimensions:n}),signal:AbortSignal.timeout(d??3e4)});if(!o.ok){let m=await o.text().catch(()=>"");throw new Error(`[Directive] OpenAI embedding failed: ${o.status}${m?` \u2013 ${m.slice(0,200)}`:""}`)}let f=(await o.json()).data[0];if(!f)throw new Error("[Directive] OpenAI embedding response contained no data entries");return f.embedding}}function q(t){let{apiKey:e,model:r="gpt-4o",maxTokens:n,baseURL:u="https://api.openai.com/v1",fetch:l=globalThis.fetch,hooks:d,temperature:h,topP:o,stop:g,responseFormat:f}=t;S(u),O(e,"createOpenAIStreamingRunner");let m=f==="json"?{type:"json_object"}:f??void 0;return async(p,s,a)=>{let c=_(d,p,s);try{let i=await l(`${u}/chat/completions`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify({model:p.model??r,...n!=null?{max_tokens:n}:{},...h!=null?{temperature:h}:{},...o!=null?{top_p:o}:{},...g!=null?{stop:g}:{},...m!=null?{response_format:m}:{},messages:[...p.instructions?[{role:"system",content:p.instructions}]:[],{role:"user",content:s}],stream:!0,stream_options:{include_usage:!0}}),signal:a.signal});i.ok||await D(i,"OpenAI");let k=I(i),{fullText:b,inputTokens:T,outputTokens:y}=await M(k,a.onToken,A=>{let x={},E=A.choices?.[0]?.delta;return E?.content&&(x.text=E.content),A.usage&&(x.inputTokens=A.usage.prompt_tokens??0,x.outputTokens=A.usage.completion_tokens??0),x},"OpenAI"),w={inputTokens:T,outputTokens:y},R=T+y;return a.onMessage?.({role:"assistant",content:b}),P(d,p,s,b,R,w,c),U(s,b,R,w)}catch(i){throw j(d,p,s,i,c),i}}}
|
|
3
|
+
exports.OPENAI_PRICING=B;exports.createOpenAIEmbedder=K;exports.createOpenAIRunner=F;exports.createOpenAIStreamingRunner=q;//# sourceMappingURL=openai.cjs.map
|
|
4
4
|
//# sourceMappingURL=openai.cjs.map
|