@stacksjs/ai 0.75.85 → 0.75.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/drivers/anthropic/index.d.ts +26 -3
- package/dist/drivers/anthropic/index.js +1 -3
- package/dist/drivers/bedrock/index.d.ts +21 -2
- package/dist/drivers/bedrock/index.js +2 -2
- package/dist/drivers/ollama/index.d.ts +16 -3
- package/dist/drivers/ollama/index.js +2 -4
- package/dist/drivers/openai/index.d.ts +17 -3
- package/dist/drivers/openai/index.js +1 -3
- package/dist/types.d.ts +12 -0
- package/dist/utils/client-bedrock-runtime.d.ts +6 -1
- package/dist/utils/client-bedrock-runtime.js +1 -1
- package/dist/utils/stream.d.ts +29 -0
- package/dist/utils/stream.js +2 -0
- package/package.json +5 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AIDriver, AIDriverConfig, AIMessage, AIResult, ChatCompletionOptions } from '../../types';
|
|
1
|
+
import type { AIDriver, AIDriverConfig, AIMessage, AIResult, AIStreamEvent, ChatCompletionOptions } from '../../types';
|
|
2
2
|
/**
|
|
3
3
|
* Configure Anthropic globally
|
|
4
4
|
*/
|
|
@@ -10,9 +10,22 @@ export declare function createAnthropicDriver(config: AnthropicDriverConfig): AI
|
|
|
10
10
|
*/
|
|
11
11
|
export declare function chat(messages: AIMessage[], options?: ChatCompletionOptions & { system?: string }): Promise<AIResult>;
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* A streamed chat completion, as events: text as it is written, each tool
|
|
14
|
+
* call once its arguments have streamed, then the whole result.
|
|
15
|
+
*
|
|
16
|
+
* A `tool_use` block streams its input as `input_json_delta` fragments, so a
|
|
17
|
+
* call is reported at its `content_block_stop`, whole. The structured-output
|
|
18
|
+
* tool is internal, as in `chat()`: its JSON streams as text. An `error`
|
|
19
|
+
* event (an overload mid-stream) throws rather than ending the stream as if
|
|
20
|
+
* the model had finished.
|
|
14
21
|
*/
|
|
15
|
-
export declare function
|
|
22
|
+
export declare function streamChatEvents(messages: AIMessage[], options?: ChatCompletionOptions & { system?: string }): AsyncGenerator<AIStreamEvent>;
|
|
23
|
+
/**
|
|
24
|
+
* Stream chat completion: the text as it is written. Tool calls, usage and
|
|
25
|
+
* the stop reason are in the result the generator returns; iterate
|
|
26
|
+
* {@link streamChatEvents} to act on a tool call as soon as it arrives.
|
|
27
|
+
*/
|
|
28
|
+
export declare function streamChat(messages: AIMessage[], options?: ChatCompletionOptions & { system?: string }): AsyncGenerator<string, AIResult, undefined>;
|
|
16
29
|
/**
|
|
17
30
|
* Simple prompt helper
|
|
18
31
|
*/
|
|
@@ -27,6 +40,7 @@ export declare const anthropic: {
|
|
|
27
40
|
configure: typeof configure;
|
|
28
41
|
chat: typeof chat;
|
|
29
42
|
streamChat: typeof streamChat;
|
|
43
|
+
streamChatEvents: typeof streamChatEvents;
|
|
30
44
|
prompt: typeof prompt;
|
|
31
45
|
estimateTokens: typeof estimateTokens;
|
|
32
46
|
createDriver: typeof createAnthropicDriver
|
|
@@ -37,4 +51,13 @@ export declare interface AnthropicDriverConfig extends AIDriverConfig {
|
|
|
37
51
|
maxTokens?: number
|
|
38
52
|
anthropicVersion?: string
|
|
39
53
|
}
|
|
54
|
+
/** The Messages API's streamed events, as far as a chat completion reads them. */
|
|
55
|
+
declare type MessagesStreamEvent = | { type: 'message_start', message: { model: string, usage?: { input_tokens?: number, output_tokens?: number } } }
|
|
56
|
+
| { type: 'content_block_start', index: number, content_block: { type: string, id?: string, name?: string } }
|
|
57
|
+
| { type: 'content_block_delta', index: number, delta: { type: 'text_delta', text: string } | { type: 'input_json_delta', partial_json: string } | { type: string } }
|
|
58
|
+
| { type: 'content_block_stop', index: number }
|
|
59
|
+
| { type: 'message_delta', delta: { stop_reason?: string }, usage?: { output_tokens?: number } }
|
|
60
|
+
| { type: 'message_stop' }
|
|
61
|
+
| { type: 'ping' }
|
|
62
|
+
| { type: 'error', error: { type: string, message: string } }
|
|
40
63
|
export default anthropic;
|
|
@@ -1,3 +1 @@
|
|
|
1
|
-
import{fetchWithRetry}from"../../utils/retry";import{recordUsage}from"../../utils/usage";import{normalizeMessagesForProvider}from"../../utils/vision";import{DEFAULT_ANTHROPIC_MODEL}from"../../models";const DEFAULT_MODEL=DEFAULT_ANTHROPIC_MODEL,DEFAULT_MAX_TOKENS=4096,DEFAULT_VERSION="2023-06-01",BASE_URL="https://api.anthropic.com/v1";let globalConfig=null;export function configure(config){globalConfig=config}function getConfig(config){const merged={...globalConfig,...config};if(!merged.apiKey)merged.apiKey=process.env.ANTHROPIC_API_KEY||"";return merged}export function createAnthropicDriver(config){const{apiKey,model=DEFAULT_MODEL,maxTokens=DEFAULT_MAX_TOKENS,anthropicVersion=DEFAULT_VERSION}=config;return{name:"Claude API",async process(command,systemPrompt,history){if(!apiKey)throw Error("Anthropic API key not set. Configure your API key in settings.");const response=await fetchWithRetry(`${BASE_URL}/messages`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":apiKey,"anthropic-version":anthropicVersion},body:JSON.stringify({model,max_tokens:maxTokens,system:systemPrompt,messages:[...history,{role:"user",content:command}]})});if(!response.ok){const error=await response.text();throw Error(`Claude API error: ${error}`)}const data=await response.json();if(!data.content||data.content.length===0)throw Error("Claude API returned empty content");return data.content[0].text},async*stream(command,systemPrompt,history){if(!apiKey)throw Error("Anthropic API key not set. Configure your API key in settings.");const response=await fetchWithRetry(`${BASE_URL}/messages`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":apiKey,"anthropic-version":anthropicVersion},body:JSON.stringify({model,max_tokens:maxTokens,system:systemPrompt,stream:!0,messages:[...history,{role:"user",content:command}]})});if(!response.ok){const error=await response.text();throw Error(`Claude API error: ${error}`)}
|
|
2
|
-
`);buffer=lines.pop()||"";for(const line of lines)if(line.startsWith("data: "))yield*handlePayload(line.slice(6))}if(buffer.startsWith("data: "))yield*handlePayload(buffer.slice(6))}}}export async function chat(messages,options={}){const config=getConfig(),{model=DEFAULT_MODEL,maxTokens=DEFAULT_MAX_TOKENS,temperature,topP,stop,system,tools,toolChoice,responseFormat}=options,normalizedMessages=normalizeMessagesForProvider(messages,"anthropic"),startedAt=Date.now(),body={model,max_tokens:maxTokens,temperature,top_p:topP,stop_sequences:stop?Array.isArray(stop)?stop:[stop]:void 0,system,messages:normalizedMessages};if(tools&&tools.length>0){body.tools=tools.map((t)=>({name:t.name,description:t.description,input_schema:t.parameters??{type:"object",properties:{}}}));if(toolChoice!==void 0)body.tool_choice=mapAnthropicToolChoice(toolChoice)}if(responseFormat&&responseFormat.type!=="text"){const outputTool=buildAnthropicJsonTool(responseFormat),existing=Array.isArray(body.tools)?body.tools:[];body.tools=[...existing,outputTool];if(toolChoice===void 0)body.tool_choice={type:"tool",name:outputTool.name}}const response=await fetchWithRetry(`${BASE_URL}/messages`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":config.apiKey,"anthropic-version":config.anthropicVersion||DEFAULT_VERSION},body:JSON.stringify(body)});if(!response.ok){const error=await response.text();throw Error(`Claude API error: ${error}`)}const data=await response.json();if(!data.content||data.content.length===0)throw Error("Claude API returned empty content");const outputTool=responseFormat&&responseFormat.type!=="text"?buildAnthropicJsonTool(responseFormat).name:void 0,blocks=data.content,structured=outputTool?blocks.find((b)=>b.type==="tool_use"&&b.name===outputTool):void 0,toolCalls=blocks.filter((b)=>b.type==="tool_use"&&b!==structured).map((b)=>({id:b.id??"",name:b.name??"",arguments:b.input??{}})),result={content:structured?JSON.stringify(structured.input):blocks.filter((b)=>b.type==="text").map((b)=>b.text??"").join(""),...toolCalls.length>0?{toolCalls}:{},model:data.model,usage:{promptTokens:data.usage?.input_tokens||0,completionTokens:data.usage?.output_tokens||0,totalTokens:(data.usage?.input_tokens||0)+(data.usage?.output_tokens||0)},finishReason:data.stop_reason};recordUsage({provider:"anthropic",model:data.model,promptTokens:result.usage.promptTokens,completionTokens:result.usage.completionTokens,totalTokens:result.usage.totalTokens,durationMs:Date.now()-startedAt,timestamp:Date.now()});return result}function mapAnthropicToolChoice(choice){if(choice==="auto")return{type:"auto"};if(choice==="required")return{type:"any"};if(choice==="none")return{type:"none"};return{type:"tool",name:choice.name}}function buildAnthropicJsonTool(format){if(format.type==="json_schema")return{name:format.json_schema.name,description:`Returns the result as JSON matching the '${format.json_schema.name}' schema.`,input_schema:format.json_schema.schema};return{name:"structured_output",description:"Returns the result as a JSON object.",input_schema:{type:"object",additionalProperties:!0}}}export async function*streamChat(messages,options={}){const config=getConfig(),{model=DEFAULT_MODEL,maxTokens=DEFAULT_MAX_TOKENS,temperature,topP,stop,system}=options,response=await fetchWithRetry(`${BASE_URL}/messages`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":config.apiKey,"anthropic-version":config.anthropicVersion||DEFAULT_VERSION},body:JSON.stringify({model,max_tokens:maxTokens,temperature,top_p:topP,stop_sequences:stop?Array.isArray(stop)?stop:[stop]:void 0,system,stream:!0,messages:normalizeMessagesForProvider(messages,"anthropic")})});if(!response.ok){const error=await response.text();throw Error(`Claude API error: ${error}`)}const reader=response.body?.getReader();if(!reader)throw Error("No response body");const decoder=new TextDecoder;let buffer="";while(!0){const{done,value}=await reader.read();if(done)break;buffer+=decoder.decode(value,{stream:!0});const lines=buffer.split(`
|
|
3
|
-
`);buffer=lines.pop()||"";for(const line of lines)if(line.startsWith("data: ")){const data=line.slice(6);if(data==="[DONE]")continue;try{const event=JSON.parse(data);if(event.type==="content_block_delta"&&event.delta?.text)yield event.delta.text}catch{}}}}export async function prompt(text,options={}){return(await chat([{role:"user",content:text}],options)).content}export function estimateTokens(text){return Math.ceil(text.length/4)}export const anthropicDriver={create:createAnthropicDriver},anthropic={configure,chat,streamChat,prompt,estimateTokens,createDriver:createAnthropicDriver};export default anthropic;
|
|
1
|
+
import{fetchWithRetry}from"../../utils/retry";import{sseJson,streamText}from"../../utils/stream";import{parseToolArguments}from"../../utils/tools";import{recordUsage}from"../../utils/usage";import{normalizeMessagesForProvider}from"../../utils/vision";import{DEFAULT_ANTHROPIC_MODEL}from"../../models";const DEFAULT_MODEL=DEFAULT_ANTHROPIC_MODEL,DEFAULT_MAX_TOKENS=4096,DEFAULT_VERSION="2023-06-01",BASE_URL="https://api.anthropic.com/v1";let globalConfig=null;export function configure(config){globalConfig=config}function getConfig(config){const merged={...globalConfig,...config};if(!merged.apiKey)merged.apiKey=process.env.ANTHROPIC_API_KEY||"";return merged}export function createAnthropicDriver(config){const{apiKey,model=DEFAULT_MODEL,maxTokens=DEFAULT_MAX_TOKENS,anthropicVersion=DEFAULT_VERSION}=config;return{name:"Claude API",async process(command,systemPrompt,history){if(!apiKey)throw Error("Anthropic API key not set. Configure your API key in settings.");const response=await fetchWithRetry(`${BASE_URL}/messages`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":apiKey,"anthropic-version":anthropicVersion},body:JSON.stringify({model,max_tokens:maxTokens,system:systemPrompt,messages:[...history,{role:"user",content:command}]})});if(!response.ok){const error=await response.text();throw Error(`Claude API error: ${error}`)}const data=await response.json();if(!data.content||data.content.length===0)throw Error("Claude API returned empty content");return data.content[0].text},async*stream(command,systemPrompt,history){if(!apiKey)throw Error("Anthropic API key not set. Configure your API key in settings.");const response=await fetchWithRetry(`${BASE_URL}/messages`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":apiKey,"anthropic-version":anthropicVersion},body:JSON.stringify({model,max_tokens:maxTokens,system:systemPrompt,stream:!0,messages:[...history,{role:"user",content:command}]})});if(!response.ok){const error=await response.text();throw Error(`Claude API error: ${error}`)}if(!response.body)throw Error("No response body");for await(const event of sseJson(response.body,"Claude API")){if(event.type==="error"){const msg=event.error?.message??JSON.stringify(event.error??event);throw Error(`[anthropic/stream] mid-stream error: ${msg}`)}if(event.type==="content_block_delta"&&event.delta?.text)yield event.delta.text}}}}function messagesRequest(messages,options){const{model=DEFAULT_MODEL,maxTokens=DEFAULT_MAX_TOKENS,temperature,topP,stop,system,tools,toolChoice,responseFormat}=options,body={model,max_tokens:maxTokens,temperature,top_p:topP,stop_sequences:stop?Array.isArray(stop)?stop:[stop]:void 0,system,messages:normalizeMessagesForProvider(messages,"anthropic")};if(tools&&tools.length>0){body.tools=tools.map((t)=>({name:t.name,description:t.description,input_schema:t.parameters??{type:"object",properties:{}}}));if(toolChoice!==void 0)body.tool_choice=mapAnthropicToolChoice(toolChoice)}if(responseFormat&&responseFormat.type!=="text"){const outputTool=buildAnthropicJsonTool(responseFormat),existing=Array.isArray(body.tools)?body.tools:[];body.tools=[...existing,outputTool];if(toolChoice===void 0)body.tool_choice={type:"tool",name:outputTool.name};return{body,outputTool:outputTool.name}}return{body}}function postMessages(body){const config=getConfig();return fetchWithRetry(`${BASE_URL}/messages`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":config.apiKey,"anthropic-version":config.anthropicVersion||DEFAULT_VERSION},body:JSON.stringify(body)})}export async function chat(messages,options={}){const startedAt=Date.now(),{body,outputTool}=messagesRequest(messages,options),response=await postMessages(body);if(!response.ok){const error=await response.text();throw Error(`Claude API error: ${error}`)}const data=await response.json();if(!data.content||data.content.length===0)throw Error("Claude API returned empty content");const blocks=data.content,structured=outputTool?blocks.find((b)=>b.type==="tool_use"&&b.name===outputTool):void 0,toolCalls=blocks.filter((b)=>b.type==="tool_use"&&b!==structured).map((b)=>({id:b.id??"",name:b.name??"",arguments:b.input??{}})),result={content:structured?JSON.stringify(structured.input):blocks.filter((b)=>b.type==="text").map((b)=>b.text??"").join(""),...toolCalls.length>0?{toolCalls}:{},model:data.model,usage:{promptTokens:data.usage?.input_tokens||0,completionTokens:data.usage?.output_tokens||0,totalTokens:(data.usage?.input_tokens||0)+(data.usage?.output_tokens||0)},finishReason:data.stop_reason};recordUsage({provider:"anthropic",model:data.model,promptTokens:result.usage.promptTokens,completionTokens:result.usage.completionTokens,totalTokens:result.usage.totalTokens,durationMs:Date.now()-startedAt,timestamp:Date.now()});return result}function mapAnthropicToolChoice(choice){if(choice==="auto")return{type:"auto"};if(choice==="required")return{type:"any"};if(choice==="none")return{type:"none"};return{type:"tool",name:choice.name}}function buildAnthropicJsonTool(format){if(format.type==="json_schema")return{name:format.json_schema.name,description:`Returns the result as JSON matching the '${format.json_schema.name}' schema.`,input_schema:format.json_schema.schema};return{name:"structured_output",description:"Returns the result as a JSON object.",input_schema:{type:"object",additionalProperties:!0}}}export async function*streamChatEvents(messages,options={}){const startedAt=Date.now(),{body,outputTool}=messagesRequest(messages,options),response=await postMessages({...body,stream:!0});if(!response.ok){const error=await response.text();throw Error(`Claude API error: ${error}`)}if(!response.body)throw Error("No response body");const toolBlocks=new Map,toolCalls=[];let content="",model=String(body.model),promptTokens=0,completionTokens=0,finishReason;for await(const event of sseJson(response.body,"Claude API"))switch(event.type){case"message_start":model=event.message.model||model;promptTokens=event.message.usage?.input_tokens??0;completionTokens=event.message.usage?.output_tokens??0;break;case"content_block_start":if(event.content_block.type==="tool_use"){const name=event.content_block.name??"";toolBlocks.set(event.index,{id:event.content_block.id??"",name,json:"",structured:name===outputTool})}break;case"content_block_delta":{const delta=event.delta;if("text"in delta&&delta.type==="text_delta"){content+=delta.text;yield{type:"text",text:delta.text}}else if("partial_json"in delta&&delta.type==="input_json_delta"){const block=toolBlocks.get(event.index);if(!block)break;block.json+=delta.partial_json;if(block.structured&&delta.partial_json){content+=delta.partial_json;yield{type:"text",text:delta.partial_json}}}break}case"content_block_stop":{const block=toolBlocks.get(event.index);if(block&&!block.structured){const call={id:block.id,name:block.name,arguments:parseToolArguments(block.json,block.name)};toolCalls.push(call);yield{type:"tool_call",call}}break}case"message_delta":finishReason=event.delta.stop_reason??finishReason;completionTokens=event.usage?.output_tokens??completionTokens;break;case"error":throw Error(`Claude API error: ${event.error.type}: ${event.error.message}`)}const result={content,...toolCalls.length>0?{toolCalls}:{},model,usage:{promptTokens,completionTokens,totalTokens:promptTokens+completionTokens},...finishReason?{finishReason}:{}};recordUsage({provider:"anthropic",model,promptTokens,completionTokens,totalTokens:promptTokens+completionTokens,durationMs:Date.now()-startedAt,timestamp:Date.now()});yield{type:"done",result}}export function streamChat(messages,options={}){return streamText(streamChatEvents(messages,options))}export async function prompt(text,options={}){return(await chat([{role:"user",content:text}],options)).content}export function estimateTokens(text){return Math.ceil(text.length/4)}export const anthropicDriver={create:createAnthropicDriver},anthropic={configure,chat,streamChat,streamChatEvents,prompt,estimateTokens,createDriver:createAnthropicDriver};export default anthropic;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AIMessage, AIResult, ChatCompletionOptions } from '../../types';
|
|
1
|
+
import type { AIMessage, AIResult, AIStreamEvent, ChatCompletionOptions } from '../../types';
|
|
2
2
|
import type { ConverseCommandInput } from '@stacksjs/ts-cloud/aws';
|
|
3
3
|
export declare function configure(config: BedrockDriverConfig): void;
|
|
4
4
|
/**
|
|
@@ -10,6 +10,23 @@ export declare function toConverseMessages(messages: AIMessage[], system?: strin
|
|
|
10
10
|
* One chat completion. `options.model` overrides the configured model.
|
|
11
11
|
*/
|
|
12
12
|
export declare function chat(messages: AIMessage[], options?: ChatCompletionOptions & { system?: string }): Promise<AIResult>;
|
|
13
|
+
/**
|
|
14
|
+
* A streamed chat completion, as events: text as it is written, each tool
|
|
15
|
+
* call once its arguments have streamed, then the whole result.
|
|
16
|
+
*
|
|
17
|
+
* Converse streams a tool call as a `contentBlockStart` carrying its id and
|
|
18
|
+
* name, then `toolUse.input` fragments of one JSON string, so a call is
|
|
19
|
+
* reported at its `contentBlockStop`, whole. The structured-output tool is
|
|
20
|
+
* internal, as in `chat()`: its JSON streams as text. Usage arrives last, in
|
|
21
|
+
* `metadata`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function streamChatEvents(messages: AIMessage[], options?: ChatCompletionOptions & { system?: string }): AsyncGenerator<AIStreamEvent>;
|
|
24
|
+
/**
|
|
25
|
+
* Stream chat completion: the text as it is written. Tool calls, usage and
|
|
26
|
+
* the stop reason are in the result the generator returns; iterate
|
|
27
|
+
* {@link streamChatEvents} to act on a tool call as soon as it arrives.
|
|
28
|
+
*/
|
|
29
|
+
export declare function streamChat(messages: AIMessage[], options?: ChatCompletionOptions & { system?: string }): AsyncGenerator<string, AIResult, undefined>;
|
|
13
30
|
/**
|
|
14
31
|
* Invocable on demand, with no inference profile and no agreement, so it
|
|
15
32
|
* answers in a fresh account. The Titan Text model this replaces as the
|
|
@@ -18,7 +35,9 @@ export declare function chat(messages: AIMessage[], options?: ChatCompletionOpti
|
|
|
18
35
|
export declare const DEFAULT_BEDROCK_MODEL: 'amazon.nova-lite-v1:0';
|
|
19
36
|
export declare const bedrock: {
|
|
20
37
|
configure: typeof configure;
|
|
21
|
-
chat: typeof chat
|
|
38
|
+
chat: typeof chat;
|
|
39
|
+
streamChat: typeof streamChat;
|
|
40
|
+
streamChatEvents: typeof streamChatEvents
|
|
22
41
|
};
|
|
23
42
|
export declare interface BedrockDriverConfig {
|
|
24
43
|
model?: string
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{converse}from"../../utils/client-bedrock-runtime";import{recordUsage}from"../../utils/usage";export const DEFAULT_BEDROCK_MODEL="amazon.nova-lite-v1:0";let globalConfig={};export function configure(config){globalConfig={...globalConfig,...config}}function resolveConfig(){return{...globalConfig,model:globalConfig.model||process.env.BEDROCK_MODEL_ID||DEFAULT_BEDROCK_MODEL}}const IMAGE_FORMATS=new Set(["png","jpeg","gif","webp"]);function imageBlock(mediaType,data){const format=mediaType.replace(/^image\//,"").replace("jpg","jpeg");if(!IMAGE_FORMATS.has(format))throw Error(`Bedrock Converse takes png, jpeg, gif or webp images, not ${mediaType}`);return{image:{format,source:{bytes:data}}}}function contentBlock(part){if(part.type==="text")return{text:part.text??""};if(part.type==="tool_call"&&part.toolCall)return{toolUse:{toolUseId:part.toolCall.id,name:part.toolCall.name,input:part.toolCall.arguments}};if(part.type==="tool_result"&&part.toolResult)return{toolResult:{toolUseId:part.toolResult.toolCallId,content:[{text:part.toolResult.content}],...part.toolResult.isError?{status:"error"}:{}}};if(part.type==="image"&&part.source)return imageBlock(part.source.media_type,part.source.data);if(part.type==="image_url"&&part.image_url){const inline=/^data:([^;]+);base64,(.*)$/.exec(part.image_url.url);if(!inline)throw Error("Bedrock Converse takes image bytes, not a URL: pass the image as a data: URL or a base64 `image` block");return imageBlock(inline[1],inline[2])}throw Error(`Unsupported message content for Bedrock: ${part.type}`)}export function toConverseMessages(messages,system){const systemBlocks=system?[{text:system}]:[],turns=[];for(const message of messages){if(message.role==="system"){const text=typeof message.content==="string"?message.content:message.content.filter((part)=>part.type==="text").map((part)=>part.text??"").join(`
|
|
2
|
-
`);if(text)systemBlocks.push({text});continue}turns.push({role:message.role,content:typeof message.content==="string"?[{text:message.content}]:message.content.map(contentBlock)})}return{messages:turns,...systemBlocks.length>0?{system:systemBlocks}:{}}}function structuredOutputTool(format){if(format.type==="json_schema")return{name:format.json_schema.name,description:`Returns the result as JSON matching the '${format.json_schema.name}' schema.`,inputSchema:{json:format.json_schema.schema}};return{name:"structured_output",description:"Returns the result as a JSON object.",inputSchema:{json:{type:"object",additionalProperties:!0}}}}function toolConfig(options){const{tools=[],toolChoice,responseFormat}=options,specs=toolChoice==="none"?[]:tools.map((tool)=>({name:tool.name,description:tool.description,inputSchema:{json:tool.parameters??{type:"object",properties:{}}}}));let choice;if(toolChoice==="required")choice={any:{}};else if(toolChoice&&typeof toolChoice==="object")choice={tool:{name:toolChoice.name}};else if(toolChoice==="auto")choice={auto:{}};if(responseFormat&&responseFormat.type!=="text"){const output=structuredOutputTool(responseFormat);specs.push(output);choice=choice??{tool:{name:output.name}}}if(specs.length===0)return;return{tools:specs.map((toolSpec)=>({toolSpec})),...choice?{toolChoice:choice}:{}}}function readOutput(output,outputTool){const blocks=output.output.message?.content??[],uses=blocks.filter((block)=>("toolUse"in block)),structured=outputTool?uses.find((block)=>block.toolUse.name===outputTool):void 0,toolCalls=uses.filter((block)=>block!==structured).map((block)=>({id:block.toolUse.toolUseId,name:block.toolUse.name,arguments:block.toolUse.input??{}}));return{content:structured?JSON.stringify(structured.toolUse.input):blocks.filter((block)=>("text"in block)).map((block)=>block.text).join(""),...toolCalls.length>0?{toolCalls}:{}}}
|
|
1
|
+
import{converse,converseStream}from"../../utils/client-bedrock-runtime";import{streamText}from"../../utils/stream";import{parseToolArguments}from"../../utils/tools";import{recordUsage}from"../../utils/usage";export const DEFAULT_BEDROCK_MODEL="amazon.nova-lite-v1:0";let globalConfig={};export function configure(config){globalConfig={...globalConfig,...config}}function resolveConfig(){return{...globalConfig,model:globalConfig.model||process.env.BEDROCK_MODEL_ID||DEFAULT_BEDROCK_MODEL}}const IMAGE_FORMATS=new Set(["png","jpeg","gif","webp"]);function imageBlock(mediaType,data){const format=mediaType.replace(/^image\//,"").replace("jpg","jpeg");if(!IMAGE_FORMATS.has(format))throw Error(`Bedrock Converse takes png, jpeg, gif or webp images, not ${mediaType}`);return{image:{format,source:{bytes:data}}}}function contentBlock(part){if(part.type==="text")return{text:part.text??""};if(part.type==="tool_call"&&part.toolCall)return{toolUse:{toolUseId:part.toolCall.id,name:part.toolCall.name,input:part.toolCall.arguments}};if(part.type==="tool_result"&&part.toolResult)return{toolResult:{toolUseId:part.toolResult.toolCallId,content:[{text:part.toolResult.content}],...part.toolResult.isError?{status:"error"}:{}}};if(part.type==="image"&&part.source)return imageBlock(part.source.media_type,part.source.data);if(part.type==="image_url"&&part.image_url){const inline=/^data:([^;]+);base64,(.*)$/.exec(part.image_url.url);if(!inline)throw Error("Bedrock Converse takes image bytes, not a URL: pass the image as a data: URL or a base64 `image` block");return imageBlock(inline[1],inline[2])}throw Error(`Unsupported message content for Bedrock: ${part.type}`)}export function toConverseMessages(messages,system){const systemBlocks=system?[{text:system}]:[],turns=[];for(const message of messages){if(message.role==="system"){const text=typeof message.content==="string"?message.content:message.content.filter((part)=>part.type==="text").map((part)=>part.text??"").join(`
|
|
2
|
+
`);if(text)systemBlocks.push({text});continue}turns.push({role:message.role,content:typeof message.content==="string"?[{text:message.content}]:message.content.map(contentBlock)})}return{messages:turns,...systemBlocks.length>0?{system:systemBlocks}:{}}}function structuredOutputTool(format){if(format.type==="json_schema")return{name:format.json_schema.name,description:`Returns the result as JSON matching the '${format.json_schema.name}' schema.`,inputSchema:{json:format.json_schema.schema}};return{name:"structured_output",description:"Returns the result as a JSON object.",inputSchema:{json:{type:"object",additionalProperties:!0}}}}function toolConfig(options){const{tools=[],toolChoice,responseFormat}=options,specs=toolChoice==="none"?[]:tools.map((tool)=>({name:tool.name,description:tool.description,inputSchema:{json:tool.parameters??{type:"object",properties:{}}}}));let choice;if(toolChoice==="required")choice={any:{}};else if(toolChoice&&typeof toolChoice==="object")choice={tool:{name:toolChoice.name}};else if(toolChoice==="auto")choice={auto:{}};if(responseFormat&&responseFormat.type!=="text"){const output=structuredOutputTool(responseFormat);specs.push(output);choice=choice??{tool:{name:output.name}}}if(specs.length===0)return;return{tools:specs.map((toolSpec)=>({toolSpec})),...choice?{toolChoice:choice}:{}}}function readOutput(output,outputTool){const blocks=output.output.message?.content??[],uses=blocks.filter((block)=>("toolUse"in block)),structured=outputTool?uses.find((block)=>block.toolUse.name===outputTool):void 0,toolCalls=uses.filter((block)=>block!==structured).map((block)=>({id:block.toolUse.toolUseId,name:block.toolUse.name,arguments:block.toolUse.input??{}}));return{content:structured?JSON.stringify(structured.toolUse.input):blocks.filter((block)=>("text"in block)).map((block)=>block.text).join(""),...toolCalls.length>0?{toolCalls}:{}}}function converseRequest(messages,options){const config=resolveConfig(),model=options.model||config.model,inferenceConfig={},maxTokens=options.maxTokens??config.maxTokens;if(maxTokens!==void 0)inferenceConfig.maxTokens=maxTokens;if(options.temperature!==void 0)inferenceConfig.temperature=options.temperature;if(options.topP!==void 0)inferenceConfig.topP=options.topP;if(options.stop!==void 0)inferenceConfig.stopSequences=Array.isArray(options.stop)?options.stop:[options.stop];const tools=toolConfig(options),outputTool=options.responseFormat&&options.responseFormat.type!=="text"?structuredOutputTool(options.responseFormat).name:void 0;return{input:{modelId:model,...toConverseMessages(messages,options.system),...Object.keys(inferenceConfig).length>0?{inferenceConfig}:{},...tools?{toolConfig:tools}:{}},...outputTool?{outputTool}:{},region:config.region}}function report(model,usage,startedAt){recordUsage({provider:"bedrock",model,promptTokens:usage.promptTokens,completionTokens:usage.completionTokens,totalTokens:usage.totalTokens,durationMs:Date.now()-startedAt,timestamp:Date.now()})}export async function chat(messages,options={}){const startedAt=Date.now(),{input,outputTool,region}=converseRequest(messages,options),response=await converse(input,region),result={...readOutput(response,outputTool),model:input.modelId,usage:{promptTokens:response.usage?.inputTokens??0,completionTokens:response.usage?.outputTokens??0,totalTokens:response.usage?.totalTokens??0},finishReason:response.stopReason};report(input.modelId,result.usage,startedAt);return result}export async function*streamChatEvents(messages,options={}){const startedAt=Date.now(),{input,outputTool,region}=converseRequest(messages,options),{stream}=await converseStream(input,region),toolBlocks=new Map,toolCalls=[];let content="",usage,finishReason;for await(const event of stream)if("contentBlockStart"in event){const use=event.contentBlockStart.start.toolUse;if(use)toolBlocks.set(event.contentBlockStart.contentBlockIndex,{id:use.toolUseId,name:use.name,json:"",structured:use.name===outputTool})}else if("contentBlockDelta"in event){const{contentBlockIndex,delta}=event.contentBlockDelta;if(delta.text){content+=delta.text;yield{type:"text",text:delta.text}}else if(delta.toolUse){const block=toolBlocks.get(contentBlockIndex);if(!block)continue;block.json+=delta.toolUse.input;if(block.structured&&delta.toolUse.input){content+=delta.toolUse.input;yield{type:"text",text:delta.toolUse.input}}}}else if("contentBlockStop"in event){const block=toolBlocks.get(event.contentBlockStop.contentBlockIndex);if(block&&!block.structured){const call={id:block.id,name:block.name,arguments:parseToolArguments(block.json,block.name)};toolCalls.push(call);yield{type:"tool_call",call}}}else if("messageStop"in event)finishReason=event.messageStop.stopReason;else if("metadata"in event)usage={promptTokens:event.metadata.usage.inputTokens,completionTokens:event.metadata.usage.outputTokens,totalTokens:event.metadata.usage.totalTokens};if(usage)report(input.modelId,usage,startedAt);yield{type:"done",result:{content,...toolCalls.length>0?{toolCalls}:{},model:input.modelId,...usage?{usage}:{},...finishReason?{finishReason}:{}}}}export function streamChat(messages,options={}){return streamText(streamChatEvents(messages,options))}export const bedrock={configure,chat,streamChat,streamChatEvents};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AIDriver, AIDriverConfig, AIMessage, AIResult, ChatCompletionOptions } from '../../types';
|
|
1
|
+
import type { AIDriver, AIDriverConfig, AIMessage, AIResult, AIStreamEvent, ChatCompletionOptions } from '../../types';
|
|
2
2
|
/**
|
|
3
3
|
* Configure Ollama globally
|
|
4
4
|
*/
|
|
@@ -9,9 +9,20 @@ export declare function createOllamaDriver(config?: OllamaDriverConfig): AIDrive
|
|
|
9
9
|
*/
|
|
10
10
|
export declare function chat(messages: AIMessage[], options?: ChatCompletionOptions): Promise<AIResult>;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* A streamed chat completion, as events: text as it is written, each tool
|
|
13
|
+
* call as it arrives, then the whole result.
|
|
14
|
+
*
|
|
15
|
+
* Ollama streams newline-delimited JSON. A tool call arrives whole, on the
|
|
16
|
+
* message of whichever chunk carries it, and the counts and stop reason on
|
|
17
|
+
* the last (`done: true`) one. An `error` line throws.
|
|
13
18
|
*/
|
|
14
|
-
export declare function
|
|
19
|
+
export declare function streamChatEvents(messages: AIMessage[], options?: ChatCompletionOptions): AsyncGenerator<AIStreamEvent>;
|
|
20
|
+
/**
|
|
21
|
+
* Stream chat completion: the text as it is written. Tool calls, usage and
|
|
22
|
+
* the stop reason are in the result the generator returns; iterate
|
|
23
|
+
* {@link streamChatEvents} to act on a tool call as soon as it arrives.
|
|
24
|
+
*/
|
|
25
|
+
export declare function streamChat(messages: AIMessage[], options?: ChatCompletionOptions): AsyncGenerator<string, AIResult, undefined>;
|
|
15
26
|
/**
|
|
16
27
|
* Generate text completion (non-chat)
|
|
17
28
|
*/
|
|
@@ -76,6 +87,7 @@ export declare const ollama: {
|
|
|
76
87
|
configure: typeof configure;
|
|
77
88
|
chat: typeof chat;
|
|
78
89
|
streamChat: typeof streamChat;
|
|
90
|
+
streamChatEvents: typeof streamChatEvents;
|
|
79
91
|
generate: typeof generate;
|
|
80
92
|
embed: typeof embed;
|
|
81
93
|
listModels: typeof listModels;
|
|
@@ -90,4 +102,5 @@ export declare interface OllamaDriverConfig extends AIDriverConfig {
|
|
|
90
102
|
model?: string
|
|
91
103
|
embeddingModel?: string
|
|
92
104
|
}
|
|
105
|
+
declare type OllamaToolCalls = Array<{ id?: string, function: { name: string, arguments: unknown } }>;
|
|
93
106
|
export default ollama;
|
|
@@ -1,4 +1,2 @@
|
|
|
1
|
-
import{parseToolArguments,toOllamaMessages}from"../../utils/tools";const DEFAULT_HOST="http://localhost:11434",DEFAULT_MODEL="llama3.2",DEFAULT_EMBEDDING_MODEL="nomic-embed-text";let globalConfig={};export function configure(config){globalConfig={...globalConfig,...config}}function getConfig(config){return{host:config?.host||globalConfig.host||process.env.OLLAMA_HOST||DEFAULT_HOST,model:config?.model||globalConfig.model||process.env.OLLAMA_MODEL||DEFAULT_MODEL,embeddingModel:config?.embeddingModel||globalConfig.embeddingModel||DEFAULT_EMBEDDING_MODEL}}export function createOllamaDriver(config={}){const{host=process.env.OLLAMA_HOST||DEFAULT_HOST,model=process.env.OLLAMA_MODEL||DEFAULT_MODEL,embeddingModel=DEFAULT_EMBEDDING_MODEL}=config;return{name:"Ollama",async process(command,systemPrompt,history){const response=await fetch(`${host}/api/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model,messages:[{role:"system",content:systemPrompt},...history,{role:"user",content:command}],stream:!1})});if(!response.ok){const error=await response.text();throw Error(`Ollama API error: ${error}`)}return(await response.json()).message.content},async*stream(command,systemPrompt,history){const response=await fetch(`${host}/api/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model,messages:[{role:"system",content:systemPrompt},...history,{role:"user",content:command}],stream:!0})});if(!response.ok){const error=await response.text();throw Error(`Ollama API error: ${error}`)}const
|
|
2
|
-
`);buffer=lines.pop()||"";for(const line of lines){if(!line.trim())continue;try{const data=JSON.parse(line);if(data.
|
|
3
|
-
`);buffer=lines.pop()||"";for(const line of lines){if(!line.trim())continue;try{const data=JSON.parse(line);if(data.message?.content)yield data.message.content}catch{}}}}export async function generate(prompt,options={}){const config=getConfig(),response=await fetch(`${config.host}/api/generate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:options.model||config.model,prompt,system:options.system,template:options.template,context:options.context,raw:options.raw,format:options.format,images:options.images,stream:!1})});if(!response.ok){const error=await response.text();throw Error(`Ollama API error: ${error}`)}const data=await response.json();return{content:data.response,model:data.model,usage:{promptTokens:data.prompt_eval_count||0,completionTokens:data.eval_count||0,totalTokens:(data.prompt_eval_count||0)+(data.eval_count||0)}}}export async function embed(input,model){const config=getConfig(),embeddingModel=model||config.embeddingModel,inputs=Array.isArray(input)?input:[input],embeddings=[];for(const text of inputs){const response=await fetch(`${config.host}/api/embeddings`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:embeddingModel,prompt:text})});if(!response.ok){const error=await response.text();throw Error(`Ollama Embeddings API error: ${error}`)}const data=await response.json();embeddings.push(data.embedding)}return Array.isArray(input)?embeddings:embeddings[0]}export async function listModels(){const config=getConfig(),response=await fetch(`${config.host}/api/tags`,{method:"GET"});if(!response.ok){const error=await response.text();throw Error(`Ollama API error: ${error}`)}return(await response.json()).models}export async function pullModel(name,onProgress){const config=getConfig(),response=await fetch(`${config.host}/api/pull`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name,stream:!0})});if(!response.ok){const error=await response.text();throw Error(`Ollama API error: ${error}`)}const reader=response.body?.getReader();if(!reader)return;const decoder=new TextDecoder;let buffer="";while(!0){const{done,value}=await reader.read();if(done)break;buffer+=decoder.decode(value,{stream:!0});const lines=buffer.split(`
|
|
4
|
-
`);buffer=lines.pop()||"";for(const line of lines){if(!line.trim())continue;try{const data=JSON.parse(line);if(onProgress)onProgress(data.status??"",data.completed,data.total)}catch{}}}}export async function deleteModel(name){const config=getConfig(),response=await fetch(`${config.host}/api/delete`,{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({name})});if(!response.ok){const error=await response.text();throw Error(`Ollama API error: ${error}`)}}export async function showModel(_name){const config=getConfig(),response=await fetch(`${config.host}/api/show`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:_name})});if(!response.ok){const error=await response.text();throw Error(`Ollama API error: ${error}`)}return response.json()}export async function isRunning(){const config=getConfig();try{return(await fetch(`${config.host}/api/tags`,{method:"GET"})).ok}catch{return!1}}export const ollamaDriver={create:createOllamaDriver},ollama={configure,chat,streamChat,generate,embed,listModels,pullModel,deleteModel,showModel,isRunning,createDriver:createOllamaDriver};export default ollama;
|
|
1
|
+
import{ndjson,streamText}from"../../utils/stream";import{parseToolArguments,toOllamaMessages}from"../../utils/tools";const DEFAULT_HOST="http://localhost:11434",DEFAULT_MODEL="llama3.2",DEFAULT_EMBEDDING_MODEL="nomic-embed-text";let globalConfig={};export function configure(config){globalConfig={...globalConfig,...config}}function getConfig(config){return{host:config?.host||globalConfig.host||process.env.OLLAMA_HOST||DEFAULT_HOST,model:config?.model||globalConfig.model||process.env.OLLAMA_MODEL||DEFAULT_MODEL,embeddingModel:config?.embeddingModel||globalConfig.embeddingModel||DEFAULT_EMBEDDING_MODEL}}export function createOllamaDriver(config={}){const{host=process.env.OLLAMA_HOST||DEFAULT_HOST,model=process.env.OLLAMA_MODEL||DEFAULT_MODEL,embeddingModel=DEFAULT_EMBEDDING_MODEL}=config;return{name:"Ollama",async process(command,systemPrompt,history){const response=await fetch(`${host}/api/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model,messages:[{role:"system",content:systemPrompt},...history,{role:"user",content:command}],stream:!1})});if(!response.ok){const error=await response.text();throw Error(`Ollama API error: ${error}`)}return(await response.json()).message.content},async*stream(command,systemPrompt,history){const response=await fetch(`${host}/api/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model,messages:[{role:"system",content:systemPrompt},...history,{role:"user",content:command}],stream:!0})});if(!response.ok){const error=await response.text();throw Error(`Ollama API error: ${error}`)}if(!response.body)throw Error("No response body");for await(const data of ndjson(response.body,"Ollama API")){if(data.error)throw Error(`Ollama API error: ${data.error}`);if(data.message?.content)yield data.message.content}},async embed(input){const inputs=Array.isArray(input)?input:[input],embeddings=[];for(const text of inputs){const response=await fetch(`${host}/api/embeddings`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:embeddingModel,prompt:text})});if(!response.ok){const error=await response.text();throw Error(`Ollama Embeddings API error: ${error}`)}const data=await response.json();embeddings.push(data.embedding)}return Array.isArray(input)?embeddings:embeddings[0]}}}function chatRequest(messages,options,stream){const config=getConfig(),{model=config.model,temperature,topP,stop,responseFormat,tools,toolChoice}=options;if(toolChoice==="required"||toolChoice&&typeof toolChoice==="object")throw Error("Ollama cannot be made to call a tool: offer the tools with toolChoice 'auto', or use another driver");const offered=toolChoice==="none"?[]:tools??[],format=responseFormat?.type==="json_schema"?responseFormat.json_schema.schema:responseFormat?.type==="json_object"?"json":void 0;return{model,messages:toOllamaMessages(messages),stream,format,...offered.length>0?{tools:offered.map((tool)=>({type:"function",function:{name:tool.name,description:tool.description,parameters:tool.parameters??{type:"object",properties:{}}}}))}:{},options:{temperature,top_p:topP,stop}}}function toToolCalls(calls,offset=0){return calls.map((call,index)=>({id:call.id??`call_${offset+index}`,name:call.function.name,arguments:parseToolArguments(call.function.arguments,call.function.name)}))}export async function chat(messages,options={}){const config=getConfig(),response=await fetch(`${config.host}/api/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(chatRequest(messages,options,!1))});if(!response.ok){const error=await response.text();throw Error(`Ollama API error: ${error}`)}const data=await response.json(),toolCalls=toToolCalls(data.message?.tool_calls??[]);return{content:data.message?.content??"",...toolCalls.length>0?{toolCalls}:{},model:data.model,usage:{promptTokens:data.prompt_eval_count||0,completionTokens:data.eval_count||0,totalTokens:(data.prompt_eval_count||0)+(data.eval_count||0)},finishReason:data.done_reason||"stop"}}export async function*streamChatEvents(messages,options={}){const config=getConfig(),body=chatRequest(messages,options,!0),response=await fetch(`${config.host}/api/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body)});if(!response.ok){const error=await response.text();throw Error(`Ollama API error: ${error}`)}if(!response.body)throw Error("No response body");const toolCalls=[];let content="",model=String(body.model),usage,finishReason;for await(const chunk of ndjson(response.body,"Ollama API")){if(chunk.error)throw Error(`Ollama API error: ${chunk.error}`);if(chunk.model)model=chunk.model;if(chunk.message?.content){content+=chunk.message.content;yield{type:"text",text:chunk.message.content}}for(const call of toToolCalls(chunk.message?.tool_calls??[],toolCalls.length)){toolCalls.push(call);yield{type:"tool_call",call}}if(chunk.done){finishReason=chunk.done_reason||"stop";usage={promptTokens:chunk.prompt_eval_count||0,completionTokens:chunk.eval_count||0,totalTokens:(chunk.prompt_eval_count||0)+(chunk.eval_count||0)}}}yield{type:"done",result:{content,...toolCalls.length>0?{toolCalls}:{},model,...usage?{usage}:{},...finishReason?{finishReason}:{}}}}export function streamChat(messages,options={}){return streamText(streamChatEvents(messages,options))}export async function generate(prompt,options={}){const config=getConfig(),response=await fetch(`${config.host}/api/generate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:options.model||config.model,prompt,system:options.system,template:options.template,context:options.context,raw:options.raw,format:options.format,images:options.images,stream:!1})});if(!response.ok){const error=await response.text();throw Error(`Ollama API error: ${error}`)}const data=await response.json();return{content:data.response,model:data.model,usage:{promptTokens:data.prompt_eval_count||0,completionTokens:data.eval_count||0,totalTokens:(data.prompt_eval_count||0)+(data.eval_count||0)}}}export async function embed(input,model){const config=getConfig(),embeddingModel=model||config.embeddingModel,inputs=Array.isArray(input)?input:[input],embeddings=[];for(const text of inputs){const response=await fetch(`${config.host}/api/embeddings`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:embeddingModel,prompt:text})});if(!response.ok){const error=await response.text();throw Error(`Ollama Embeddings API error: ${error}`)}const data=await response.json();embeddings.push(data.embedding)}return Array.isArray(input)?embeddings:embeddings[0]}export async function listModels(){const config=getConfig(),response=await fetch(`${config.host}/api/tags`,{method:"GET"});if(!response.ok){const error=await response.text();throw Error(`Ollama API error: ${error}`)}return(await response.json()).models}export async function pullModel(name,onProgress){const config=getConfig(),response=await fetch(`${config.host}/api/pull`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name,stream:!0})});if(!response.ok){const error=await response.text();throw Error(`Ollama API error: ${error}`)}const reader=response.body?.getReader();if(!reader)return;const decoder=new TextDecoder;let buffer="";while(!0){const{done,value}=await reader.read();if(done)break;buffer+=decoder.decode(value,{stream:!0});const lines=buffer.split(`
|
|
2
|
+
`);buffer=lines.pop()||"";for(const line of lines){if(!line.trim())continue;try{const data=JSON.parse(line);if(onProgress)onProgress(data.status??"",data.completed,data.total)}catch{}}}}export async function deleteModel(name){const config=getConfig(),response=await fetch(`${config.host}/api/delete`,{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({name})});if(!response.ok){const error=await response.text();throw Error(`Ollama API error: ${error}`)}}export async function showModel(_name){const config=getConfig(),response=await fetch(`${config.host}/api/show`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:_name})});if(!response.ok){const error=await response.text();throw Error(`Ollama API error: ${error}`)}return response.json()}export async function isRunning(){const config=getConfig();try{return(await fetch(`${config.host}/api/tags`,{method:"GET"})).ok}catch{return!1}}export const ollamaDriver={create:createOllamaDriver},ollama={configure,chat,streamChat,streamChatEvents,generate,embed,listModels,pullModel,deleteModel,showModel,isRunning,createDriver:createOllamaDriver};export default ollama;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AIDriver, AIDriverConfig, AIMessage, AIResult, ChatCompletionOptions } from '../../types';
|
|
1
|
+
import type { AIDriver, AIDriverConfig, AIMessage, AIResult, AIStreamEvent, ChatCompletionOptions } from '../../types';
|
|
2
2
|
/**
|
|
3
3
|
* Configure OpenAI globally
|
|
4
4
|
*/
|
|
@@ -9,9 +9,22 @@ export declare function createOpenAIDriver(config: OpenAIDriverConfig): AIDriver
|
|
|
9
9
|
*/
|
|
10
10
|
export declare function chat(messages: AIMessage[], options?: ChatCompletionOptions): Promise<AIResult>;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* A streamed chat completion, as events: text as it is written, each tool
|
|
13
|
+
* call once its arguments have streamed, then the whole result.
|
|
14
|
+
*
|
|
15
|
+
* A tool call streams as `delta.tool_calls` pieces keyed by `index`: the id
|
|
16
|
+
* and name first, then the arguments a fragment at a time. The calls are
|
|
17
|
+
* complete when the choice finishes, and are reported then, in index order.
|
|
18
|
+
* Usage only comes with `stream_options.include_usage`, on a last chunk with
|
|
19
|
+
* no choices, so it is asked for. An `error` chunk throws.
|
|
13
20
|
*/
|
|
14
|
-
export declare function
|
|
21
|
+
export declare function streamChatEvents(messages: AIMessage[], options?: ChatCompletionOptions): AsyncGenerator<AIStreamEvent>;
|
|
22
|
+
/**
|
|
23
|
+
* Stream chat completion: the text as it is written. Tool calls, usage and
|
|
24
|
+
* the stop reason are in the result the generator returns; iterate
|
|
25
|
+
* {@link streamChatEvents} to act on a tool call as soon as it arrives.
|
|
26
|
+
*/
|
|
27
|
+
export declare function streamChat(messages: AIMessage[], options?: ChatCompletionOptions): AsyncGenerator<string, AIResult, undefined>;
|
|
15
28
|
/**
|
|
16
29
|
* Create embeddings
|
|
17
30
|
*/
|
|
@@ -50,6 +63,7 @@ export declare const openai: {
|
|
|
50
63
|
configure: typeof configure;
|
|
51
64
|
chat: typeof chat;
|
|
52
65
|
streamChat: typeof streamChat;
|
|
66
|
+
streamChatEvents: typeof streamChatEvents;
|
|
53
67
|
embed: typeof embed;
|
|
54
68
|
generateImage: typeof generateImage;
|
|
55
69
|
transcribe: typeof transcribe;
|
|
@@ -1,3 +1 @@
|
|
|
1
|
-
import{fetchWithRetry}from"../../utils/retry";import{parseToolArguments,toChatCompletionMessages}from"../../utils/tools";import{recordUsage}from"../../utils/usage";const DEFAULT_MODEL="gpt-4o",DEFAULT_MAX_TOKENS=4096,DEFAULT_EMBEDDING_MODEL="text-embedding-3-small",DEFAULT_BASE_URL="https://api.openai.com/v1";let globalConfig=null;export function configure(config){globalConfig=config}function getConfig(config){const merged={...globalConfig,...config};if(!merged.apiKey)merged.apiKey=process.env.OPENAI_API_KEY||"";return merged}export function createOpenAIDriver(config){const{apiKey,model=DEFAULT_MODEL,maxTokens=DEFAULT_MAX_TOKENS,baseUrl=DEFAULT_BASE_URL,embeddingModel=DEFAULT_EMBEDDING_MODEL}=config;return{name:"OpenAI",async process(command,systemPrompt,history){if(!apiKey)throw Error("OpenAI API key not set. Configure your API key in settings.");const response=await fetchWithRetry(`${baseUrl}/chat/completions`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${apiKey}`},body:JSON.stringify({model,max_tokens:maxTokens,messages:[{role:"system",content:systemPrompt},...history,{role:"user",content:command}]})});if(!response.ok){const error=await response.text();throw Error(`OpenAI API error: ${error}`)}const data=await response.json();if(!data.choices||data.choices.length===0)throw Error("OpenAI API returned empty choices");return data.choices[0].message.content},async*stream(command,systemPrompt,history){if(!apiKey)throw Error("OpenAI API key not set. Configure your API key in settings.");const response=await fetchWithRetry(`${baseUrl}/chat/completions`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${apiKey}`},body:JSON.stringify({model,max_tokens:maxTokens,stream:!0,messages:[{role:"system",content:systemPrompt},...history,{role:"user",content:command}]})});if(!response.ok){const error=await response.text();throw Error(`OpenAI API error: ${error}`)}const
|
|
2
|
-
`);buffer=lines.pop()||"";for(const line of lines)if(line.startsWith("data: "))yield*handlePayload(line.slice(6))}if(buffer.startsWith("data: "))yield*handlePayload(buffer.slice(6))},async embed(input){if(!apiKey)throw Error("OpenAI API key not set. Configure your API key in settings.");const response=await fetchWithRetry(`${baseUrl}/embeddings`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${apiKey}`},body:JSON.stringify({model:embeddingModel,input})});if(!response.ok){const error=await response.text();throw Error(`OpenAI Embeddings API error: ${error}`)}const data=await response.json();if(Array.isArray(input))return data.data.map((d)=>d.embedding);return data.data[0].embedding}}}export async function chat(messages,options={}){const config=getConfig(),{model=DEFAULT_MODEL,maxTokens=DEFAULT_MAX_TOKENS,temperature,topP,stop,tools,toolChoice,responseFormat}=options,normalizedMessages=toChatCompletionMessages(messages,{argumentsAsString:!0}),startedAt=Date.now(),body={model,max_tokens:maxTokens,temperature,top_p:topP,stop,messages:normalizedMessages};if(tools?.length)body.tools=tools.map((tool)=>({type:"function",function:{name:tool.name,description:tool.description,parameters:tool.parameters??{type:"object",properties:{}}}}));if(toolChoice!==void 0)body.tool_choice=typeof toolChoice==="object"?{type:"function",function:{name:toolChoice.name}}:toolChoice;if(responseFormat&&responseFormat.type!=="text")body.response_format=responseFormat;const response=await fetchWithRetry(`${config.baseUrl||DEFAULT_BASE_URL}/chat/completions`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${config.apiKey}`},body:JSON.stringify(body)});if(!response.ok){const error=await response.text();throw Error(`OpenAI API error: ${error}`)}const data=await response.json();if(!data.choices||data.choices.length===0)throw Error("OpenAI API returned empty choices");const message=data.choices[0].message,toolCalls=(message.tool_calls??[]).map((call)=>({id:call.id,name:call.function.name,arguments:parseToolArguments(call.function.arguments,call.function.name)})),result={content:message.content??"",...toolCalls.length>0?{toolCalls}:{},model:data.model,usage:{promptTokens:data.usage?.prompt_tokens||0,completionTokens:data.usage?.completion_tokens||0,totalTokens:data.usage?.total_tokens||0},finishReason:data.choices[0].finish_reason};recordUsage({provider:"openai",model:data.model,promptTokens:result.usage.promptTokens,completionTokens:result.usage.completionTokens,totalTokens:result.usage.totalTokens,durationMs:Date.now()-startedAt,timestamp:Date.now()});return result}export async function*streamChat(messages,options={}){const config=getConfig(),{model=DEFAULT_MODEL,maxTokens=DEFAULT_MAX_TOKENS,temperature,topP,stop}=options,response=await fetchWithRetry(`${config.baseUrl||DEFAULT_BASE_URL}/chat/completions`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${config.apiKey}`},body:JSON.stringify({model,max_tokens:maxTokens,temperature,top_p:topP,stop,stream:!0,messages:toChatCompletionMessages(messages,{argumentsAsString:!0})})});if(!response.ok){const error=await response.text();throw Error(`OpenAI API error: ${error}`)}const reader=response.body?.getReader();if(!reader)throw Error("No response body");const decoder=new TextDecoder;let buffer="";while(!0){const{done,value}=await reader.read();if(done)break;buffer+=decoder.decode(value,{stream:!0});const lines=buffer.split(`
|
|
3
|
-
`);buffer=lines.pop()||"";for(const line of lines)if(line.startsWith("data: ")){const data=line.slice(6);if(data==="[DONE]")continue;try{const content=JSON.parse(data).choices[0]?.delta?.content;if(content)yield content}catch{}}}}export async function embed(input,model=DEFAULT_EMBEDDING_MODEL){const config=getConfig(),response=await fetchWithRetry(`${config.baseUrl||DEFAULT_BASE_URL}/embeddings`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${config.apiKey}`},body:JSON.stringify({model,input})});if(!response.ok){const error=await response.text();throw Error(`OpenAI Embeddings API error: ${error}`)}const data=await response.json();if(Array.isArray(input))return data.data.map((d)=>d.embedding);return data.data[0].embedding}export async function generateImage(prompt,options={}){const config=getConfig(),{model="dall-e-3",size="1024x1024",quality="standard",n=1,responseFormat="url"}=options,response=await fetchWithRetry(`${config.baseUrl||DEFAULT_BASE_URL}/images/generations`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${config.apiKey}`},body:JSON.stringify({model,prompt,size,quality,n,response_format:responseFormat})});if(!response.ok){const error=await response.text();throw Error(`OpenAI Image API error: ${error}`)}return(await response.json()).data}export async function transcribe(audioFile,options={}){const config=getConfig(),formData=new FormData;formData.append("file",audioFile);formData.append("model",options.model||"whisper-1");if(options.language)formData.append("language",options.language);if(options.prompt)formData.append("prompt",options.prompt);if(options.responseFormat)formData.append("response_format",options.responseFormat);if(options.temperature!==void 0)formData.append("temperature",String(options.temperature));const response=await fetchWithRetry(`${config.baseUrl||DEFAULT_BASE_URL}/audio/transcriptions`,{method:"POST",headers:{Authorization:`Bearer ${config.apiKey}`},body:formData});if(!response.ok){const error=await response.text();throw Error(`OpenAI Whisper API error: ${error}`)}return response.json()}export async function textToSpeech(input,options={}){const config=getConfig(),{model="tts-1",voice="alloy",responseFormat="mp3",speed=1}=options,response=await fetchWithRetry(`${config.baseUrl||DEFAULT_BASE_URL}/audio/speech`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${config.apiKey}`},body:JSON.stringify({model,input,voice,response_format:responseFormat,speed})});if(!response.ok){const error=await response.text();throw Error(`OpenAI TTS API error: ${error}`)}return response.arrayBuffer()}export const openaiDriver={create:createOpenAIDriver},openai={configure,chat,streamChat,embed,generateImage,transcribe,textToSpeech,createDriver:createOpenAIDriver};export default openai;
|
|
1
|
+
import{fetchWithRetry}from"../../utils/retry";import{sseJson,streamText}from"../../utils/stream";import{parseToolArguments,toChatCompletionMessages}from"../../utils/tools";import{recordUsage}from"../../utils/usage";const DEFAULT_MODEL="gpt-4o",DEFAULT_MAX_TOKENS=4096,DEFAULT_EMBEDDING_MODEL="text-embedding-3-small",DEFAULT_BASE_URL="https://api.openai.com/v1";let globalConfig=null;export function configure(config){globalConfig=config}function getConfig(config){const merged={...globalConfig,...config};if(!merged.apiKey)merged.apiKey=process.env.OPENAI_API_KEY||"";return merged}export function createOpenAIDriver(config){const{apiKey,model=DEFAULT_MODEL,maxTokens=DEFAULT_MAX_TOKENS,baseUrl=DEFAULT_BASE_URL,embeddingModel=DEFAULT_EMBEDDING_MODEL}=config;return{name:"OpenAI",async process(command,systemPrompt,history){if(!apiKey)throw Error("OpenAI API key not set. Configure your API key in settings.");const response=await fetchWithRetry(`${baseUrl}/chat/completions`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${apiKey}`},body:JSON.stringify({model,max_tokens:maxTokens,messages:[{role:"system",content:systemPrompt},...history,{role:"user",content:command}]})});if(!response.ok){const error=await response.text();throw Error(`OpenAI API error: ${error}`)}const data=await response.json();if(!data.choices||data.choices.length===0)throw Error("OpenAI API returned empty choices");return data.choices[0].message.content},async*stream(command,systemPrompt,history){if(!apiKey)throw Error("OpenAI API key not set. Configure your API key in settings.");const response=await fetchWithRetry(`${baseUrl}/chat/completions`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${apiKey}`},body:JSON.stringify({model,max_tokens:maxTokens,stream:!0,messages:[{role:"system",content:systemPrompt},...history,{role:"user",content:command}]})});if(!response.ok){const error=await response.text();throw Error(`OpenAI API error: ${error}`)}if(!response.body)throw Error("No response body");for await(const parsed of sseJson(response.body,"OpenAI API")){if(parsed?.error){const msg=parsed.error.message||JSON.stringify(parsed.error);throw Error(`[openai/stream] mid-stream error: ${msg}`)}const content=parsed.choices?.[0]?.delta?.content;if(content)yield content}},async embed(input){if(!apiKey)throw Error("OpenAI API key not set. Configure your API key in settings.");const response=await fetchWithRetry(`${baseUrl}/embeddings`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${apiKey}`},body:JSON.stringify({model:embeddingModel,input})});if(!response.ok){const error=await response.text();throw Error(`OpenAI Embeddings API error: ${error}`)}const data=await response.json();if(Array.isArray(input))return data.data.map((d)=>d.embedding);return data.data[0].embedding}}}function completionRequest(messages,options){const{model=DEFAULT_MODEL,maxTokens=DEFAULT_MAX_TOKENS,temperature,topP,stop,tools,toolChoice,responseFormat}=options,body={model,max_tokens:maxTokens,temperature,top_p:topP,stop,messages:toChatCompletionMessages(messages,{argumentsAsString:!0})};if(tools?.length)body.tools=tools.map((tool)=>({type:"function",function:{name:tool.name,description:tool.description,parameters:tool.parameters??{type:"object",properties:{}}}}));if(toolChoice!==void 0)body.tool_choice=typeof toolChoice==="object"?{type:"function",function:{name:toolChoice.name}}:toolChoice;if(responseFormat&&responseFormat.type!=="text")body.response_format=responseFormat;return body}function postCompletion(body){const config=getConfig();return fetchWithRetry(`${config.baseUrl||DEFAULT_BASE_URL}/chat/completions`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${config.apiKey}`},body:JSON.stringify(body)})}export async function chat(messages,options={}){const startedAt=Date.now(),response=await postCompletion(completionRequest(messages,options));if(!response.ok){const error=await response.text();throw Error(`OpenAI API error: ${error}`)}const data=await response.json();if(!data.choices||data.choices.length===0)throw Error("OpenAI API returned empty choices");const message=data.choices[0].message,toolCalls=(message.tool_calls??[]).map((call)=>({id:call.id,name:call.function.name,arguments:parseToolArguments(call.function.arguments,call.function.name)})),result={content:message.content??"",...toolCalls.length>0?{toolCalls}:{},model:data.model,usage:{promptTokens:data.usage?.prompt_tokens||0,completionTokens:data.usage?.completion_tokens||0,totalTokens:data.usage?.total_tokens||0},finishReason:data.choices[0].finish_reason};recordUsage({provider:"openai",model:data.model,promptTokens:result.usage.promptTokens,completionTokens:result.usage.completionTokens,totalTokens:result.usage.totalTokens,durationMs:Date.now()-startedAt,timestamp:Date.now()});return result}export async function*streamChatEvents(messages,options={}){const startedAt=Date.now(),body=completionRequest(messages,options),response=await postCompletion({...body,stream:!0,stream_options:{include_usage:!0}});if(!response.ok){const error=await response.text();throw Error(`OpenAI API error: ${error}`)}if(!response.body)throw Error("No response body");const pending=new Map,toolCalls=[];let content="",model=String(body.model),usage,finishReason;function*flushCalls(){for(const index of[...pending.keys()].sort((a,b)=>a-b)){const piece=pending.get(index),call={id:piece.id,name:piece.name,arguments:parseToolArguments(piece.args,piece.name)};toolCalls.push(call);yield{type:"tool_call",call}}pending.clear()}for await(const chunk of sseJson(response.body,"OpenAI API")){if(chunk.error)throw Error(`OpenAI API error: ${chunk.error.type?`${chunk.error.type}: `:""}${chunk.error.message??"stream error"}`);if(chunk.model)model=chunk.model;if(chunk.usage)usage={promptTokens:chunk.usage.prompt_tokens??0,completionTokens:chunk.usage.completion_tokens??0,totalTokens:chunk.usage.total_tokens??0};const choice=chunk.choices?.[0];if(!choice)continue;if(choice.delta?.content){content+=choice.delta.content;yield{type:"text",text:choice.delta.content}}for(const piece of choice.delta?.tool_calls??[]){const entry=pending.get(piece.index)??{id:"",name:"",args:""};if(piece.id)entry.id=piece.id;if(piece.function?.name)entry.name+=piece.function.name;if(piece.function?.arguments)entry.args+=piece.function.arguments;pending.set(piece.index,entry)}if(choice.finish_reason){finishReason=choice.finish_reason;yield*flushCalls()}}yield*flushCalls();const result={content,...toolCalls.length>0?{toolCalls}:{},model,...usage?{usage}:{},...finishReason?{finishReason}:{}};if(usage)recordUsage({provider:"openai",model,...usage,durationMs:Date.now()-startedAt,timestamp:Date.now()});yield{type:"done",result}}export function streamChat(messages,options={}){return streamText(streamChatEvents(messages,options))}export async function embed(input,model=DEFAULT_EMBEDDING_MODEL){const config=getConfig(),response=await fetchWithRetry(`${config.baseUrl||DEFAULT_BASE_URL}/embeddings`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${config.apiKey}`},body:JSON.stringify({model,input})});if(!response.ok){const error=await response.text();throw Error(`OpenAI Embeddings API error: ${error}`)}const data=await response.json();if(Array.isArray(input))return data.data.map((d)=>d.embedding);return data.data[0].embedding}export async function generateImage(prompt,options={}){const config=getConfig(),{model="dall-e-3",size="1024x1024",quality="standard",n=1,responseFormat="url"}=options,response=await fetchWithRetry(`${config.baseUrl||DEFAULT_BASE_URL}/images/generations`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${config.apiKey}`},body:JSON.stringify({model,prompt,size,quality,n,response_format:responseFormat})});if(!response.ok){const error=await response.text();throw Error(`OpenAI Image API error: ${error}`)}return(await response.json()).data}export async function transcribe(audioFile,options={}){const config=getConfig(),formData=new FormData;formData.append("file",audioFile);formData.append("model",options.model||"whisper-1");if(options.language)formData.append("language",options.language);if(options.prompt)formData.append("prompt",options.prompt);if(options.responseFormat)formData.append("response_format",options.responseFormat);if(options.temperature!==void 0)formData.append("temperature",String(options.temperature));const response=await fetchWithRetry(`${config.baseUrl||DEFAULT_BASE_URL}/audio/transcriptions`,{method:"POST",headers:{Authorization:`Bearer ${config.apiKey}`},body:formData});if(!response.ok){const error=await response.text();throw Error(`OpenAI Whisper API error: ${error}`)}return response.json()}export async function textToSpeech(input,options={}){const config=getConfig(),{model="tts-1",voice="alloy",responseFormat="mp3",speed=1}=options,response=await fetchWithRetry(`${config.baseUrl||DEFAULT_BASE_URL}/audio/speech`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${config.apiKey}`},body:JSON.stringify({model,input,voice,response_format:responseFormat,speed})});if(!response.ok){const error=await response.text();throw Error(`OpenAI TTS API error: ${error}`)}return response.arrayBuffer()}export const openaiDriver={create:createOpenAIDriver},openai={configure,chat,streamChat,streamChatEvents,embed,generateImage,transcribe,textToSpeech,createDriver:createOpenAIDriver};export default openai;
|
package/dist/types.d.ts
CHANGED
|
@@ -234,6 +234,18 @@ export type AIResponseFormat = | { type: 'text' }
|
|
|
234
234
|
strict?: boolean
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* One event of a streamed chat completion, the same from every driver.
|
|
239
|
+
*
|
|
240
|
+
* `text` arrives in pieces as the model writes. A `tool_call` arrives whole,
|
|
241
|
+
* once its arguments have finished streaming, so it can be run as soon as
|
|
242
|
+
* it is seen. `done` comes last, exactly once, with everything the stream
|
|
243
|
+
* carried in the shape `chat()` returns - text, tool calls, usage and why
|
|
244
|
+
* the model stopped.
|
|
245
|
+
*/
|
|
246
|
+
export type AIStreamEvent = | { type: 'text', text: string }
|
|
247
|
+
| { type: 'tool_call', call: AIToolCall }
|
|
248
|
+
| { type: 'done', result: AIResult }
|
|
237
249
|
export type AIProvider = 'anthropic' | 'openai' | 'ollama' | 'bedrock';
|
|
238
250
|
/**
|
|
239
251
|
* Where a provider's credentials come from. `aws-credential-chain`: Bedrock
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConverseCommandInput, ConverseCommandOutput, InvokeModelCommandInput, InvokeModelCommandOutput, InvokeModelWithResponseStreamCommandInput, InvokeModelWithResponseStreamCommandOutput } from '@stacksjs/ts-cloud/aws';
|
|
1
|
+
import type { ConverseCommandInput, ConverseCommandOutput, ConverseStreamCommandOutput, InvokeModelCommandInput, InvokeModelCommandOutput, InvokeModelWithResponseStreamCommandInput, InvokeModelWithResponseStreamCommandOutput } from '@stacksjs/ts-cloud/aws';
|
|
2
2
|
export type { ConverseCommandInput, ConverseCommandOutput, InvokeModelCommandInput, InvokeModelWithResponseStreamCommandInput };
|
|
3
3
|
/**
|
|
4
4
|
* The region Bedrock is called in: the one passed, then `AWS_REGION`, then
|
|
@@ -13,6 +13,11 @@ export declare function bedrockRegion(region?: string): string;
|
|
|
13
13
|
* @see https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html
|
|
14
14
|
*/
|
|
15
15
|
export declare function converse(params: ConverseCommandInput, region?: string): Promise<ConverseCommandOutput>;
|
|
16
|
+
/*
|
|
17
|
+
* Converse, streamed: the same request, answered as events
|
|
18
|
+
* @see https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ConverseStream.html
|
|
19
|
+
*/
|
|
20
|
+
export declare function converseStream(params: ConverseCommandInput, region?: string): Promise<ConverseStreamCommandOutput>;
|
|
16
21
|
/*
|
|
17
22
|
* Invoke Model
|
|
18
23
|
* @param {InvokeModelCommandInput} params
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import process from"node:process";export function bedrockRegion(region){return region||process.env.AWS_REGION||process.env.AWS_DEFAULT_REGION||"us-east-1"}const clients=new Map;async function getClient(region){const resolved=bedrockRegion(region),cached=clients.get(resolved);if(cached)return cached;const mod=await import("@stacksjs/ts-cloud/aws");if(!mod?.BedrockRuntimeClient)throw Error("@stacksjs/ts-cloud/aws does not export BedrockRuntimeClient - rebuild ts-cloud or remove the AI dependency.");const client=new mod.BedrockRuntimeClient(resolved);clients.set(resolved,client);return client}export async function converse(params,region){return(await getClient(region)).converse(params)}export async function invokeModel(params){return(await getClient()).invokeModel(params)}export async function invokeModelWithResponseStream(params){return(await getClient()).invokeModelWithResponseStream(params)}
|
|
1
|
+
import process from"node:process";export function bedrockRegion(region){return region||process.env.AWS_REGION||process.env.AWS_DEFAULT_REGION||"us-east-1"}const clients=new Map;async function getClient(region){const resolved=bedrockRegion(region),cached=clients.get(resolved);if(cached)return cached;const mod=await import("@stacksjs/ts-cloud/aws");if(!mod?.BedrockRuntimeClient)throw Error("@stacksjs/ts-cloud/aws does not export BedrockRuntimeClient - rebuild ts-cloud or remove the AI dependency.");const client=new mod.BedrockRuntimeClient(resolved);clients.set(resolved,client);return client}export async function converse(params,region){return(await getClient(region)).converse(params)}export async function converseStream(params,region){const c=await getClient(region);if(typeof c.converseStream!=="function")throw Error("This @stacksjs/ts-cloud cannot stream the Converse API: upgrade it to 0.16.44 or newer.");return c.converseStream(params)}export async function invokeModel(params){return(await getClient()).invokeModel(params)}export async function invokeModelWithResponseStream(params){return(await getClient()).invokeModelWithResponseStream(params)}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AIResult, AIStreamEvent } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Plumbing shared by the drivers' streamed chat completions.
|
|
4
|
+
*
|
|
5
|
+
* Each driver parses its provider's wire format into {@link AIStreamEvent}s
|
|
6
|
+
* once, in `streamChatEvents()`. `streamChat()` is the text-only view of that
|
|
7
|
+
* same stream, so a caller that only wants the words keeps writing
|
|
8
|
+
* `for await (const text of streamChat(...))` and nothing about it changed -
|
|
9
|
+
* while a tool call the model makes mid-stream, which used to vanish, is in
|
|
10
|
+
* the result the generator returns.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* The text of an event stream, returning the stream's final result.
|
|
14
|
+
*
|
|
15
|
+
* `for await` reads the text and ignores the return value, which is what
|
|
16
|
+
* keeps every existing caller working. A caller that wants the tool calls
|
|
17
|
+
* too can take the return value, or iterate `streamChatEvents()` instead.
|
|
18
|
+
*/
|
|
19
|
+
export declare function streamText(events: AsyncIterable<AIStreamEvent>): AsyncGenerator<string, AIResult, undefined>;
|
|
20
|
+
/**
|
|
21
|
+
* The JSON objects of a server-sent event stream: each `data:` payload,
|
|
22
|
+
* parsed. OpenAI's `[DONE]` sentinel ends nothing on its own and is skipped.
|
|
23
|
+
*
|
|
24
|
+
* A payload that is not JSON throws. Skipping it, as the drivers used to,
|
|
25
|
+
* silently drops whatever text or tool-call fragment it carried.
|
|
26
|
+
*/
|
|
27
|
+
export declare function sseJson(body: ReadableStream<Uint8Array>, provider: string): AsyncGenerator<any>;
|
|
28
|
+
/** The JSON objects of a newline-delimited JSON stream, as Ollama sends. */
|
|
29
|
+
export declare function ndjson(body: ReadableStream<Uint8Array>, provider: string): AsyncGenerator<any>;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export async function*streamText(events){let result;for await(const event of events)if(event.type==="text")yield event.text;else if(event.type==="done")result=event.result;if(!result)throw Error("The stream ended without a result");return result}async function*lines(body){const decoder=new TextDecoder;let buffer="";for await(const chunk of body){buffer+=decoder.decode(chunk,{stream:!0});const parts=buffer.split(`
|
|
2
|
+
`);buffer=parts.pop()??"";for(const line of parts)yield line.endsWith("\r")?line.slice(0,-1):line}buffer+=decoder.decode();if(buffer)yield buffer.endsWith("\r")?buffer.slice(0,-1):buffer}export async function*sseJson(body,provider){for await(const line of lines(body)){if(!line.startsWith("data:"))continue;const data=line.slice(5).trimStart();if(data==="[DONE]")continue;yield parseEvent(data,provider)}}export async function*ndjson(body,provider){for await(const line of lines(body))if(line.trim())yield parseEvent(line,provider)}function parseEvent(data,provider){try{return JSON.parse(data)}catch{throw Error(`${provider} sent a stream event that is not JSON: ${data.slice(0,200)}`)}}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/ai",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.87",
|
|
6
6
|
"description": "Stacks Artificial Intelligence.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -65,10 +65,10 @@
|
|
|
65
65
|
"prepublishOnly": "bun run build"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@stacksjs/cli": "0.75.
|
|
69
|
-
"@stacksjs/config": "0.75.
|
|
70
|
-
"@stacksjs/storage": "0.75.
|
|
71
|
-
"@stacksjs/ts-cloud": "^0.16.
|
|
68
|
+
"@stacksjs/cli": "0.75.87",
|
|
69
|
+
"@stacksjs/config": "0.75.87",
|
|
70
|
+
"@stacksjs/storage": "0.75.87",
|
|
71
|
+
"@stacksjs/ts-cloud": "^0.16.44"
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|
|
74
74
|
"@anthropic-ai/claude-agent-sdk": "^0.2.79"
|