@yolk-sdk/agent 0.1.0-canary.21 → 0.1.0-canary.23
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/README.md +36 -8
- package/dist/loop/run.d.mts.map +1 -1
- package/dist/loop/run.mjs +38 -19
- package/dist/loop/run.mjs.map +1 -1
- package/dist/protocol/content.d.mts +2 -1
- package/dist/protocol/content.d.mts.map +1 -1
- package/dist/protocol/content.mjs +8 -1
- package/dist/protocol/content.mjs.map +1 -1
- package/dist/protocol/index.d.mts +3 -3
- package/dist/protocol/index.d.mts.map +1 -1
- package/dist/protocol/index.mjs +3 -3
- package/dist/protocol/message.d.mts +20 -1
- package/dist/protocol/message.d.mts.map +1 -1
- package/dist/protocol/message.mjs +59 -1
- package/dist/protocol/message.mjs.map +1 -1
- package/dist/providers/anthropic/claude-provider.d.mts +6 -0
- package/dist/providers/anthropic/claude-provider.d.mts.map +1 -1
- package/dist/providers/anthropic/claude-provider.mjs +47 -24
- package/dist/providers/anthropic/claude-provider.mjs.map +1 -1
- package/dist/providers/openai/codex-provider.d.mts.map +1 -1
- package/dist/providers/openai/codex-provider.mjs +4 -2
- package/dist/providers/openai/codex-provider.mjs.map +1 -1
- package/dist/providers/openai/provider.d.mts.map +1 -1
- package/dist/providers/openai/provider.mjs +4 -2
- package/dist/providers/openai/provider.mjs.map +1 -1
- package/dist/providers/transcript.d.mts +9 -0
- package/dist/providers/transcript.d.mts.map +1 -0
- package/dist/providers/transcript.mjs +17 -0
- package/dist/providers/transcript.mjs.map +1 -0
- package/package.json +1 -1
- package/src/loop/run.ts +56 -17
- package/src/protocol/content.ts +11 -0
- package/src/protocol/index.ts +10 -1
- package/src/protocol/message.ts +127 -0
- package/src/providers/anthropic/claude-provider.ts +78 -34
- package/src/providers/openai/codex-provider.ts +4 -1
- package/src/providers/openai/provider.ts +4 -2
- package/src/providers/transcript.ts +24 -0
- package/src/tools/README.md +3 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { classifyProviderFailure, providerErrorInfo, providerFailureCause, providerFailureRetryable } from "../provider-error.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { validateProviderTranscript } from "../transcript.mjs";
|
|
3
|
+
import { AgentInputUsage, AgentOutputUsage, AgentUsage, ToolCall, assistantContent, assistantHostToolCalls, attachmentSourceText, attachmentSourceUrl, isTextDocumentMimeType, messageContextText, prependMessageContextToContent } from "@yolk-sdk/agent/protocol";
|
|
3
4
|
import { Config, Context, Effect, Layer, Option, Redacted, Stream } from "effect";
|
|
4
5
|
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
5
6
|
import * as Schema from "effect/Schema";
|
|
@@ -71,7 +72,7 @@ const contentPartToUserPart = (part) => {
|
|
|
71
72
|
type: "text",
|
|
72
73
|
text: part.text
|
|
73
74
|
});
|
|
74
|
-
case "Image": return Option.match(
|
|
75
|
+
case "Image": return Option.match(attachmentSourceUrl(part.source, part.mimeType), {
|
|
75
76
|
onNone: () => Effect.fail(unsupportedContentError("Unresolved image source")),
|
|
76
77
|
onSome: (url) => Effect.succeed({
|
|
77
78
|
type: "image_url",
|
|
@@ -138,6 +139,7 @@ const toOpenAiTool = (tool) => ({
|
|
|
138
139
|
}
|
|
139
140
|
});
|
|
140
141
|
const toOpenAiRequestBody = (request, config) => Effect.gen(function* () {
|
|
142
|
+
yield* validateProviderTranscript(request.messages);
|
|
141
143
|
const messages = [{
|
|
142
144
|
role: "system",
|
|
143
145
|
content: request.systemPrompt
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.mjs","names":["LLMTextDelta"],"sources":["../../../src/providers/openai/provider.ts"],"sourcesContent":["import { Config, Context, Effect, Layer, Option, Redacted, Stream } from 'effect'\nimport {\n FetchHttpClient,\n HttpClient,\n HttpClientRequest,\n type HttpClientError,\n type HttpClientResponse\n} from 'effect/unstable/http'\nimport * as Schema from 'effect/Schema'\nimport {\n ToolCall,\n AgentInputUsage,\n AgentOutputUsage,\n AgentUsage,\n attachmentSourceDataUrl,\n attachmentSourceText,\n assistantContent,\n assistantHostToolCalls,\n isTextDocumentMimeType,\n messageContextText,\n prependMessageContextToContent,\n type AgentMessage,\n type Content,\n type ContentPart,\n type ToolDef\n} from '@yolk-sdk/agent/protocol'\nimport {\n LLMError,\n LLMDone,\n LLMProvider,\n LLMTextDelta,\n LLMToolCall,\n LLMUsage,\n type LLMEvent,\n type LLMRequest\n} from '@yolk-sdk/agent/loop'\nimport {\n classifyProviderFailure,\n providerErrorInfo,\n providerFailureCause,\n providerFailureRetryable\n} from '../provider-error.ts'\n\nexport type OpenAiProviderConfig = {\n readonly chatCompletionsUrl?: string\n readonly maxCompletionTokens?: number\n readonly extraHeaders?: Readonly<Record<string, string>>\n readonly apiKey: Redacted.Redacted<string>\n}\n\ntype OpenAiTextContentPart = {\n readonly type: 'text'\n readonly text: string\n}\n\ntype OpenAiImageContentPart = {\n readonly type: 'image_url'\n readonly image_url: {\n readonly url: string\n }\n}\n\ntype OpenAiUserContent = string | ReadonlyArray<OpenAiTextContentPart | OpenAiImageContentPart>\n\ntype OpenAiToolCall = {\n readonly id: string\n readonly type: 'function'\n readonly function: {\n readonly name: string\n readonly arguments: string\n }\n}\n\ntype OpenAiMessage =\n | { readonly role: 'system'; readonly content: string }\n | { readonly role: 'user'; readonly content: OpenAiUserContent }\n | {\n readonly role: 'assistant'\n readonly content: string | null\n readonly tool_calls?: ReadonlyArray<OpenAiToolCall>\n }\n | { readonly role: 'tool'; readonly tool_call_id: string; readonly content: string }\n\ntype OpenAiTool = {\n readonly type: 'function'\n readonly function: {\n readonly name: string\n readonly description: string\n readonly parameters: unknown\n }\n}\n\ntype OpenAiRequestBody = {\n readonly model: string\n readonly messages: ReadonlyArray<OpenAiMessage>\n readonly max_completion_tokens: number\n readonly tools?: ReadonlyArray<OpenAiTool>\n readonly parallel_tool_calls?: true\n}\n\nclass OpenAiFunctionResponse extends Schema.Class<OpenAiFunctionResponse>('OpenAiFunctionResponse')(\n {\n name: Schema.String,\n arguments: Schema.String\n }\n) {}\n\nclass OpenAiToolCallResponse extends Schema.Class<OpenAiToolCallResponse>('OpenAiToolCallResponse')(\n {\n id: Schema.String,\n type: Schema.Literals(['function']),\n function: OpenAiFunctionResponse\n }\n) {}\n\nclass OpenAiMessageResponse extends Schema.Class<OpenAiMessageResponse>('OpenAiMessageResponse')({\n content: Schema.NullOr(Schema.String),\n tool_calls: Schema.optional(Schema.Array(OpenAiToolCallResponse))\n}) {}\n\nclass OpenAiChoiceResponse extends Schema.Class<OpenAiChoiceResponse>('OpenAiChoiceResponse')({\n message: OpenAiMessageResponse\n}) {}\n\nclass OpenAiPromptTokensDetails extends Schema.Class<OpenAiPromptTokensDetails>(\n 'OpenAiPromptTokensDetails'\n)({\n cached_tokens: Schema.optional(Schema.Number)\n}) {}\n\nclass OpenAiCompletionTokensDetails extends Schema.Class<OpenAiCompletionTokensDetails>(\n 'OpenAiCompletionTokensDetails'\n)({\n reasoning_tokens: Schema.optional(Schema.Number)\n}) {}\n\nclass OpenAiUsageResponse extends Schema.Class<OpenAiUsageResponse>('OpenAiUsageResponse')({\n prompt_tokens: Schema.Number,\n completion_tokens: Schema.Number,\n prompt_tokens_details: Schema.optional(OpenAiPromptTokensDetails),\n completion_tokens_details: Schema.optional(OpenAiCompletionTokensDetails)\n}) {}\n\nclass OpenAiChatCompletionResponse extends Schema.Class<OpenAiChatCompletionResponse>(\n 'OpenAiChatCompletionResponse'\n)({\n choices: Schema.Array(OpenAiChoiceResponse),\n usage: Schema.optional(OpenAiUsageResponse)\n}) {}\n\nclass OpenAiConfig extends Context.Service<OpenAiConfig, OpenAiProviderConfig>()(\n '@app/OpenAiConfig'\n) {}\n\nconst OpenAiConfigLayer = Layer.effect(\n OpenAiConfig,\n Effect.gen(function* () {\n const apiKey = yield* Config.redacted('OPENAI_API_KEY')\n return { apiKey }\n }).pipe(\n Effect.mapError(\n () =>\n new LLMError({\n cause: 'provider_error',\n message: 'OPENAI_API_KEY not found',\n retryable: false\n })\n )\n )\n)\n\nconst unknownToMessage = (error: unknown) =>\n error instanceof Error ? error.message : String(error)\n\nconst encodeJsonString = (value: unknown, message: string) =>\n Schema.encodeUnknownEffect(Schema.UnknownFromJsonString)(value).pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'provider_error',\n message: `${message}: ${unknownToMessage(error)}`,\n retryable: false\n })\n )\n )\n\nconst decodeJsonString = (raw: string, message: string) =>\n Schema.decodeUnknownEffect(Schema.UnknownFromJsonString)(raw).pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'invalid_response',\n message: `${message}: ${unknownToMessage(error)}`,\n retryable: false\n })\n )\n )\n\nconst unsupportedContentError = (contentType: string) =>\n new LLMError({\n cause: 'provider_error',\n message: `${contentType} content is not supported by the OpenAI provider yet`,\n retryable: false\n })\n\nconst textDocumentToOpenAiPart = (part: Extract<ContentPart, { readonly _tag: 'Document' }>) =>\n attachmentSourceText(part.source).pipe(\n Effect.mapError(() => unsupportedContentError('Invalid document text')),\n Effect.flatMap(\n Option.match({\n onNone: () => Effect.fail(unsupportedContentError('Unresolved document source')),\n onSome: text => {\n const block: OpenAiTextContentPart = {\n type: 'text',\n text: `Document: ${part.title ?? part.filename}\\n\\n${text}`\n }\n\n return Effect.succeed(block)\n }\n })\n )\n )\n\nconst contentPartToUserPart = (\n part: ContentPart\n): Effect.Effect<OpenAiTextContentPart | OpenAiImageContentPart, LLMError> => {\n switch (part._tag) {\n case 'Text':\n return Effect.succeed({ type: 'text', text: part.text })\n case 'Image':\n return Option.match(attachmentSourceDataUrl(part.source, part.mimeType), {\n onNone: () => Effect.fail(unsupportedContentError('Unresolved image source')),\n onSome: url => Effect.succeed({ type: 'image_url', image_url: { url } })\n })\n case 'Document':\n return isTextDocumentMimeType(part.mimeType)\n ? textDocumentToOpenAiPart(part)\n : Effect.fail(unsupportedContentError('Document'))\n case 'Audio':\n return Effect.fail(unsupportedContentError('Audio'))\n }\n}\n\nconst contentToUserContent = (content: Content): Effect.Effect<OpenAiUserContent, LLMError> =>\n typeof content === 'string'\n ? Effect.succeed(content)\n : Effect.forEach(content, contentPartToUserPart)\n\nconst contentPartToText = (part: ContentPart, owner: string): Effect.Effect<string, LLMError> => {\n switch (part._tag) {\n case 'Text':\n return Effect.succeed(part.text)\n case 'Image':\n return Effect.fail(unsupportedContentError(`${owner} image`))\n case 'Document':\n return Effect.fail(unsupportedContentError(`${owner} document`))\n case 'Audio':\n return Effect.fail(unsupportedContentError(`${owner} audio`))\n }\n}\n\nconst contentToText = (content: Content, owner: string): Effect.Effect<string, LLMError> =>\n typeof content === 'string'\n ? Effect.succeed(content)\n : Effect.forEach(content, part => contentPartToText(part, owner)).pipe(\n Effect.map(textParts => textParts.join('\\n'))\n )\n\nconst serializeToolArguments = (params: unknown) =>\n encodeJsonString(params, 'Could not serialize OpenAI tool arguments')\n\nconst toolCallToOpenAiToolCall = (call: ToolCall): Effect.Effect<OpenAiToolCall, LLMError> =>\n Effect.gen(function* () {\n return {\n id: call.id,\n type: 'function',\n function: {\n name: call.name,\n arguments: yield* serializeToolArguments(call.params)\n }\n }\n })\n\nconst toOpenAiMessage = (message: AgentMessage): Effect.Effect<OpenAiMessage, LLMError> =>\n Effect.gen(function* () {\n switch (message._tag) {\n case 'User':\n return {\n role: 'user',\n content: yield* contentToUserContent(\n prependMessageContextToContent(message.content, messageContextText(message))\n )\n }\n case 'Assistant': {\n const content = prependMessageContextToContent(\n assistantContent(message),\n messageContextText(message)\n )\n const toolCalls = yield* Effect.forEach(\n assistantHostToolCalls(message),\n toolCallToOpenAiToolCall\n )\n\n if (toolCalls.length > 0) {\n return {\n role: 'assistant',\n content: yield* contentToText(content, 'Assistant'),\n tool_calls: toolCalls\n }\n }\n\n return {\n role: 'assistant',\n content: yield* contentToText(content, 'Assistant')\n }\n }\n case 'ToolResult':\n return {\n role: 'tool',\n tool_call_id: message.toolCallId,\n content: yield* contentToText(\n prependMessageContextToContent(message.content, messageContextText(message)),\n 'Tool result'\n )\n }\n }\n })\n\nconst toOpenAiTool = (tool: ToolDef): OpenAiTool => ({\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters\n }\n})\n\nexport const toOpenAiRequestBody = (\n request: LLMRequest,\n config?: { readonly maxCompletionTokens?: number }\n): Effect.Effect<OpenAiRequestBody, LLMError> =>\n Effect.gen(function* () {\n const systemMessage: OpenAiMessage = { role: 'system', content: request.systemPrompt }\n const requestMessages = yield* Effect.forEach(request.messages, toOpenAiMessage)\n const messages = [systemMessage, ...requestMessages]\n\n const body = {\n model: request.model,\n messages,\n max_completion_tokens: config?.maxCompletionTokens ?? 4096\n }\n\n if (request.tools.length === 0) {\n return body\n }\n\n return {\n ...body,\n tools: request.tools.map(toOpenAiTool),\n parallel_tool_calls: true\n }\n })\n\nconst parseToolArguments = (raw: string) =>\n decodeJsonString(raw, 'Invalid OpenAI tool arguments JSON')\n\nconst toLlmEvents = (\n message: OpenAiMessageResponse\n): Effect.Effect<ReadonlyArray<LLMEvent>, LLMError> =>\n Effect.gen(function* () {\n const content = message.content ?? ''\n const textEvents = content.length > 0 ? [LLMTextDelta.make({ text: content })] : []\n const toolCallEvents = yield* Effect.forEach(message.tool_calls ?? [], call =>\n parseToolArguments(call.function.arguments).pipe(\n Effect.map(params =>\n LLMToolCall.make({\n call: ToolCall.make({\n id: call.id,\n name: call.function.name,\n params\n })\n })\n )\n )\n )\n\n if (toolCallEvents.length > 0) {\n return [...textEvents, ...toolCallEvents, LLMDone.make({ stopReason: 'tool_use' })]\n }\n\n return [...textEvents, LLMDone.make({ stopReason: 'stop' })]\n })\n\nconst toAgentUsage = (usage: OpenAiUsageResponse) =>\n AgentUsage.make({\n input: AgentInputUsage.make({\n total: usage.prompt_tokens,\n uncached: usage.prompt_tokens - (usage.prompt_tokens_details?.cached_tokens ?? 0),\n cacheRead: usage.prompt_tokens_details?.cached_tokens\n }),\n output: AgentOutputUsage.make({\n total: usage.completion_tokens,\n reasoning: usage.completion_tokens_details?.reasoning_tokens,\n text: usage.completion_tokens - (usage.completion_tokens_details?.reasoning_tokens ?? 0)\n })\n })\n\nconst toHttpClientLlmError =\n (message: string, retryable: boolean) => (error: HttpClientError.HttpClientError) =>\n new LLMError({\n cause: 'provider_error',\n message: `${message}: ${error.message}`,\n retryable,\n provider: providerErrorInfo({\n provider: 'openai',\n kind: retryable ? 'network' : 'unknown'\n })\n })\n\nconst parseOpenAiResponseJson = (\n response: HttpClientResponse.HttpClientResponse\n): Effect.Effect<unknown, LLMError> =>\n response.json.pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'invalid_response',\n message: `Could not parse OpenAI response JSON: ${error.message}`,\n retryable: false\n })\n )\n )\n\nconst sendOpenAiRequest = (\n config: OpenAiProviderConfig,\n request: LLMRequest,\n client: HttpClient.HttpClient\n): Effect.Effect<ReadonlyArray<LLMEvent>, LLMError> =>\n Effect.gen(function* () {\n const body = yield* toOpenAiRequestBody(request, config)\n const serializedBody = yield* encodeJsonString(body, 'Could not serialize OpenAI request')\n\n const httpRequest = HttpClientRequest.post(config.chatCompletionsUrl ?? 'https://api.openai.com/v1/chat/completions').pipe(\n HttpClientRequest.setHeaders({\n accept: 'application/json',\n authorization: `Bearer ${Redacted.value(config.apiKey)}`,\n 'content-type': 'application/json',\n ...config.extraHeaders\n }),\n HttpClientRequest.bodyText(serializedBody, 'application/json')\n )\n const response = yield* client\n .execute(httpRequest)\n .pipe(Effect.mapError(toHttpClientLlmError('OpenAI request failed', true)))\n\n if (response.status < 200 || response.status >= 300) {\n const errorText = yield* response.text.pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'provider_error',\n message: `Could not read OpenAI error body: ${error.message}`,\n retryable: false\n })\n )\n )\n\n const provider = classifyProviderFailure({\n provider: 'openai',\n status: response.status,\n headers: response.headers,\n body: errorText\n })\n\n return yield* Effect.fail(\n new LLMError({\n cause: providerFailureCause(provider.kind),\n message: `OpenAI returned ${response.status}`,\n retryable: providerFailureRetryable(provider.kind),\n provider\n })\n )\n }\n\n const json = yield* parseOpenAiResponseJson(response)\n const parsed = yield* Schema.decodeUnknownEffect(OpenAiChatCompletionResponse)(json).pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'invalid_response',\n message: `Invalid OpenAI response: ${unknownToMessage(error)}`,\n retryable: false\n })\n )\n )\n const choice = parsed.choices[0]\n\n if (choice === undefined) {\n return yield* Effect.fail(\n new LLMError({\n cause: 'invalid_response',\n message: 'OpenAI response contained no choices',\n retryable: false\n })\n )\n }\n\n const events = yield* toLlmEvents(choice.message)\n\n if (parsed.usage === undefined) {\n return events\n }\n\n return [...events, LLMUsage.make({ usage: toAgentUsage(parsed.usage) })]\n }).pipe(Effect.withSpan('OpenAiProvider.stream'))\n\nexport const makeOpenAiProviderLayer = (config: OpenAiProviderConfig) =>\n Layer.effect(\n LLMProvider,\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n\n return LLMProvider.of({\n stream: request =>\n Stream.fromEffect(sendOpenAiRequest(config, request, client)).pipe(\n Stream.flatMap(events => Stream.fromIterable(events))\n )\n })\n })\n )\n\nexport const OpenAiProviderLayer = Layer.effect(\n LLMProvider,\n Effect.gen(function* () {\n const config = yield* OpenAiConfig\n const client = yield* HttpClient.HttpClient\n\n return LLMProvider.of({\n stream: request =>\n Stream.fromEffect(sendOpenAiRequest(config, request, client)).pipe(\n Stream.flatMap(events => Stream.fromIterable(events))\n )\n })\n })\n).pipe(Layer.provide(Layer.mergeAll(OpenAiConfigLayer, FetchHttpClient.layer)))\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"provider.mjs","names":["LLMTextDelta"],"sources":["../../../src/providers/openai/provider.ts"],"sourcesContent":["import { Config, Context, Effect, Layer, Option, Redacted, Stream } from 'effect'\nimport {\n FetchHttpClient,\n HttpClient,\n HttpClientRequest,\n type HttpClientError,\n type HttpClientResponse\n} from 'effect/unstable/http'\nimport * as Schema from 'effect/Schema'\nimport {\n ToolCall,\n AgentInputUsage,\n AgentOutputUsage,\n AgentUsage,\n attachmentSourceText,\n attachmentSourceUrl,\n assistantContent,\n assistantHostToolCalls,\n isTextDocumentMimeType,\n messageContextText,\n prependMessageContextToContent,\n type AgentMessage,\n type Content,\n type ContentPart,\n type ToolDef\n} from '@yolk-sdk/agent/protocol'\nimport {\n LLMError,\n LLMDone,\n LLMProvider,\n LLMTextDelta,\n LLMToolCall,\n LLMUsage,\n type LLMEvent,\n type LLMRequest\n} from '@yolk-sdk/agent/loop'\nimport {\n classifyProviderFailure,\n providerErrorInfo,\n providerFailureCause,\n providerFailureRetryable\n} from '../provider-error.ts'\nimport { validateProviderTranscript } from '../transcript.ts'\n\nexport type OpenAiProviderConfig = {\n readonly chatCompletionsUrl?: string\n readonly maxCompletionTokens?: number\n readonly extraHeaders?: Readonly<Record<string, string>>\n readonly apiKey: Redacted.Redacted<string>\n}\n\ntype OpenAiTextContentPart = {\n readonly type: 'text'\n readonly text: string\n}\n\ntype OpenAiImageContentPart = {\n readonly type: 'image_url'\n readonly image_url: {\n readonly url: string\n }\n}\n\ntype OpenAiUserContent = string | ReadonlyArray<OpenAiTextContentPart | OpenAiImageContentPart>\n\ntype OpenAiToolCall = {\n readonly id: string\n readonly type: 'function'\n readonly function: {\n readonly name: string\n readonly arguments: string\n }\n}\n\ntype OpenAiMessage =\n | { readonly role: 'system'; readonly content: string }\n | { readonly role: 'user'; readonly content: OpenAiUserContent }\n | {\n readonly role: 'assistant'\n readonly content: string | null\n readonly tool_calls?: ReadonlyArray<OpenAiToolCall>\n }\n | { readonly role: 'tool'; readonly tool_call_id: string; readonly content: string }\n\ntype OpenAiTool = {\n readonly type: 'function'\n readonly function: {\n readonly name: string\n readonly description: string\n readonly parameters: unknown\n }\n}\n\ntype OpenAiRequestBody = {\n readonly model: string\n readonly messages: ReadonlyArray<OpenAiMessage>\n readonly max_completion_tokens: number\n readonly tools?: ReadonlyArray<OpenAiTool>\n readonly parallel_tool_calls?: true\n}\n\nclass OpenAiFunctionResponse extends Schema.Class<OpenAiFunctionResponse>('OpenAiFunctionResponse')(\n {\n name: Schema.String,\n arguments: Schema.String\n }\n) {}\n\nclass OpenAiToolCallResponse extends Schema.Class<OpenAiToolCallResponse>('OpenAiToolCallResponse')(\n {\n id: Schema.String,\n type: Schema.Literals(['function']),\n function: OpenAiFunctionResponse\n }\n) {}\n\nclass OpenAiMessageResponse extends Schema.Class<OpenAiMessageResponse>('OpenAiMessageResponse')({\n content: Schema.NullOr(Schema.String),\n tool_calls: Schema.optional(Schema.Array(OpenAiToolCallResponse))\n}) {}\n\nclass OpenAiChoiceResponse extends Schema.Class<OpenAiChoiceResponse>('OpenAiChoiceResponse')({\n message: OpenAiMessageResponse\n}) {}\n\nclass OpenAiPromptTokensDetails extends Schema.Class<OpenAiPromptTokensDetails>(\n 'OpenAiPromptTokensDetails'\n)({\n cached_tokens: Schema.optional(Schema.Number)\n}) {}\n\nclass OpenAiCompletionTokensDetails extends Schema.Class<OpenAiCompletionTokensDetails>(\n 'OpenAiCompletionTokensDetails'\n)({\n reasoning_tokens: Schema.optional(Schema.Number)\n}) {}\n\nclass OpenAiUsageResponse extends Schema.Class<OpenAiUsageResponse>('OpenAiUsageResponse')({\n prompt_tokens: Schema.Number,\n completion_tokens: Schema.Number,\n prompt_tokens_details: Schema.optional(OpenAiPromptTokensDetails),\n completion_tokens_details: Schema.optional(OpenAiCompletionTokensDetails)\n}) {}\n\nclass OpenAiChatCompletionResponse extends Schema.Class<OpenAiChatCompletionResponse>(\n 'OpenAiChatCompletionResponse'\n)({\n choices: Schema.Array(OpenAiChoiceResponse),\n usage: Schema.optional(OpenAiUsageResponse)\n}) {}\n\nclass OpenAiConfig extends Context.Service<OpenAiConfig, OpenAiProviderConfig>()(\n '@app/OpenAiConfig'\n) {}\n\nconst OpenAiConfigLayer = Layer.effect(\n OpenAiConfig,\n Effect.gen(function* () {\n const apiKey = yield* Config.redacted('OPENAI_API_KEY')\n return { apiKey }\n }).pipe(\n Effect.mapError(\n () =>\n new LLMError({\n cause: 'provider_error',\n message: 'OPENAI_API_KEY not found',\n retryable: false\n })\n )\n )\n)\n\nconst unknownToMessage = (error: unknown) =>\n error instanceof Error ? error.message : String(error)\n\nconst encodeJsonString = (value: unknown, message: string) =>\n Schema.encodeUnknownEffect(Schema.UnknownFromJsonString)(value).pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'provider_error',\n message: `${message}: ${unknownToMessage(error)}`,\n retryable: false\n })\n )\n )\n\nconst decodeJsonString = (raw: string, message: string) =>\n Schema.decodeUnknownEffect(Schema.UnknownFromJsonString)(raw).pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'invalid_response',\n message: `${message}: ${unknownToMessage(error)}`,\n retryable: false\n })\n )\n )\n\nconst unsupportedContentError = (contentType: string) =>\n new LLMError({\n cause: 'provider_error',\n message: `${contentType} content is not supported by the OpenAI provider yet`,\n retryable: false\n })\n\nconst textDocumentToOpenAiPart = (part: Extract<ContentPart, { readonly _tag: 'Document' }>) =>\n attachmentSourceText(part.source).pipe(\n Effect.mapError(() => unsupportedContentError('Invalid document text')),\n Effect.flatMap(\n Option.match({\n onNone: () => Effect.fail(unsupportedContentError('Unresolved document source')),\n onSome: text => {\n const block: OpenAiTextContentPart = {\n type: 'text',\n text: `Document: ${part.title ?? part.filename}\\n\\n${text}`\n }\n\n return Effect.succeed(block)\n }\n })\n )\n )\n\nconst contentPartToUserPart = (\n part: ContentPart\n): Effect.Effect<OpenAiTextContentPart | OpenAiImageContentPart, LLMError> => {\n switch (part._tag) {\n case 'Text':\n return Effect.succeed({ type: 'text', text: part.text })\n case 'Image':\n return Option.match(attachmentSourceUrl(part.source, part.mimeType), {\n onNone: () => Effect.fail(unsupportedContentError('Unresolved image source')),\n onSome: url => Effect.succeed({ type: 'image_url', image_url: { url } })\n })\n case 'Document':\n return isTextDocumentMimeType(part.mimeType)\n ? textDocumentToOpenAiPart(part)\n : Effect.fail(unsupportedContentError('Document'))\n case 'Audio':\n return Effect.fail(unsupportedContentError('Audio'))\n }\n}\n\nconst contentToUserContent = (content: Content): Effect.Effect<OpenAiUserContent, LLMError> =>\n typeof content === 'string'\n ? Effect.succeed(content)\n : Effect.forEach(content, contentPartToUserPart)\n\nconst contentPartToText = (part: ContentPart, owner: string): Effect.Effect<string, LLMError> => {\n switch (part._tag) {\n case 'Text':\n return Effect.succeed(part.text)\n case 'Image':\n return Effect.fail(unsupportedContentError(`${owner} image`))\n case 'Document':\n return Effect.fail(unsupportedContentError(`${owner} document`))\n case 'Audio':\n return Effect.fail(unsupportedContentError(`${owner} audio`))\n }\n}\n\nconst contentToText = (content: Content, owner: string): Effect.Effect<string, LLMError> =>\n typeof content === 'string'\n ? Effect.succeed(content)\n : Effect.forEach(content, part => contentPartToText(part, owner)).pipe(\n Effect.map(textParts => textParts.join('\\n'))\n )\n\nconst serializeToolArguments = (params: unknown) =>\n encodeJsonString(params, 'Could not serialize OpenAI tool arguments')\n\nconst toolCallToOpenAiToolCall = (call: ToolCall): Effect.Effect<OpenAiToolCall, LLMError> =>\n Effect.gen(function* () {\n return {\n id: call.id,\n type: 'function',\n function: {\n name: call.name,\n arguments: yield* serializeToolArguments(call.params)\n }\n }\n })\n\nconst toOpenAiMessage = (message: AgentMessage): Effect.Effect<OpenAiMessage, LLMError> =>\n Effect.gen(function* () {\n switch (message._tag) {\n case 'User':\n return {\n role: 'user',\n content: yield* contentToUserContent(\n prependMessageContextToContent(message.content, messageContextText(message))\n )\n }\n case 'Assistant': {\n const content = prependMessageContextToContent(\n assistantContent(message),\n messageContextText(message)\n )\n const toolCalls = yield* Effect.forEach(\n assistantHostToolCalls(message),\n toolCallToOpenAiToolCall\n )\n\n if (toolCalls.length > 0) {\n return {\n role: 'assistant',\n content: yield* contentToText(content, 'Assistant'),\n tool_calls: toolCalls\n }\n }\n\n return {\n role: 'assistant',\n content: yield* contentToText(content, 'Assistant')\n }\n }\n case 'ToolResult':\n return {\n role: 'tool',\n tool_call_id: message.toolCallId,\n content: yield* contentToText(\n prependMessageContextToContent(message.content, messageContextText(message)),\n 'Tool result'\n )\n }\n }\n })\n\nconst toOpenAiTool = (tool: ToolDef): OpenAiTool => ({\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters\n }\n})\n\nexport const toOpenAiRequestBody = (\n request: LLMRequest,\n config?: { readonly maxCompletionTokens?: number }\n): Effect.Effect<OpenAiRequestBody, LLMError> =>\n Effect.gen(function* () {\n yield* validateProviderTranscript(request.messages)\n const systemMessage: OpenAiMessage = { role: 'system', content: request.systemPrompt }\n const requestMessages = yield* Effect.forEach(request.messages, toOpenAiMessage)\n const messages = [systemMessage, ...requestMessages]\n\n const body = {\n model: request.model,\n messages,\n max_completion_tokens: config?.maxCompletionTokens ?? 4096\n }\n\n if (request.tools.length === 0) {\n return body\n }\n\n return {\n ...body,\n tools: request.tools.map(toOpenAiTool),\n parallel_tool_calls: true\n }\n })\n\nconst parseToolArguments = (raw: string) =>\n decodeJsonString(raw, 'Invalid OpenAI tool arguments JSON')\n\nconst toLlmEvents = (\n message: OpenAiMessageResponse\n): Effect.Effect<ReadonlyArray<LLMEvent>, LLMError> =>\n Effect.gen(function* () {\n const content = message.content ?? ''\n const textEvents = content.length > 0 ? [LLMTextDelta.make({ text: content })] : []\n const toolCallEvents = yield* Effect.forEach(message.tool_calls ?? [], call =>\n parseToolArguments(call.function.arguments).pipe(\n Effect.map(params =>\n LLMToolCall.make({\n call: ToolCall.make({\n id: call.id,\n name: call.function.name,\n params\n })\n })\n )\n )\n )\n\n if (toolCallEvents.length > 0) {\n return [...textEvents, ...toolCallEvents, LLMDone.make({ stopReason: 'tool_use' })]\n }\n\n return [...textEvents, LLMDone.make({ stopReason: 'stop' })]\n })\n\nconst toAgentUsage = (usage: OpenAiUsageResponse) =>\n AgentUsage.make({\n input: AgentInputUsage.make({\n total: usage.prompt_tokens,\n uncached: usage.prompt_tokens - (usage.prompt_tokens_details?.cached_tokens ?? 0),\n cacheRead: usage.prompt_tokens_details?.cached_tokens\n }),\n output: AgentOutputUsage.make({\n total: usage.completion_tokens,\n reasoning: usage.completion_tokens_details?.reasoning_tokens,\n text: usage.completion_tokens - (usage.completion_tokens_details?.reasoning_tokens ?? 0)\n })\n })\n\nconst toHttpClientLlmError =\n (message: string, retryable: boolean) => (error: HttpClientError.HttpClientError) =>\n new LLMError({\n cause: 'provider_error',\n message: `${message}: ${error.message}`,\n retryable,\n provider: providerErrorInfo({\n provider: 'openai',\n kind: retryable ? 'network' : 'unknown'\n })\n })\n\nconst parseOpenAiResponseJson = (\n response: HttpClientResponse.HttpClientResponse\n): Effect.Effect<unknown, LLMError> =>\n response.json.pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'invalid_response',\n message: `Could not parse OpenAI response JSON: ${error.message}`,\n retryable: false\n })\n )\n )\n\nconst sendOpenAiRequest = (\n config: OpenAiProviderConfig,\n request: LLMRequest,\n client: HttpClient.HttpClient\n): Effect.Effect<ReadonlyArray<LLMEvent>, LLMError> =>\n Effect.gen(function* () {\n const body = yield* toOpenAiRequestBody(request, config)\n const serializedBody = yield* encodeJsonString(body, 'Could not serialize OpenAI request')\n\n const httpRequest = HttpClientRequest.post(config.chatCompletionsUrl ?? 'https://api.openai.com/v1/chat/completions').pipe(\n HttpClientRequest.setHeaders({\n accept: 'application/json',\n authorization: `Bearer ${Redacted.value(config.apiKey)}`,\n 'content-type': 'application/json',\n ...config.extraHeaders\n }),\n HttpClientRequest.bodyText(serializedBody, 'application/json')\n )\n const response = yield* client\n .execute(httpRequest)\n .pipe(Effect.mapError(toHttpClientLlmError('OpenAI request failed', true)))\n\n if (response.status < 200 || response.status >= 300) {\n const errorText = yield* response.text.pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'provider_error',\n message: `Could not read OpenAI error body: ${error.message}`,\n retryable: false\n })\n )\n )\n\n const provider = classifyProviderFailure({\n provider: 'openai',\n status: response.status,\n headers: response.headers,\n body: errorText\n })\n\n return yield* Effect.fail(\n new LLMError({\n cause: providerFailureCause(provider.kind),\n message: `OpenAI returned ${response.status}`,\n retryable: providerFailureRetryable(provider.kind),\n provider\n })\n )\n }\n\n const json = yield* parseOpenAiResponseJson(response)\n const parsed = yield* Schema.decodeUnknownEffect(OpenAiChatCompletionResponse)(json).pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'invalid_response',\n message: `Invalid OpenAI response: ${unknownToMessage(error)}`,\n retryable: false\n })\n )\n )\n const choice = parsed.choices[0]\n\n if (choice === undefined) {\n return yield* Effect.fail(\n new LLMError({\n cause: 'invalid_response',\n message: 'OpenAI response contained no choices',\n retryable: false\n })\n )\n }\n\n const events = yield* toLlmEvents(choice.message)\n\n if (parsed.usage === undefined) {\n return events\n }\n\n return [...events, LLMUsage.make({ usage: toAgentUsage(parsed.usage) })]\n }).pipe(Effect.withSpan('OpenAiProvider.stream'))\n\nexport const makeOpenAiProviderLayer = (config: OpenAiProviderConfig) =>\n Layer.effect(\n LLMProvider,\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n\n return LLMProvider.of({\n stream: request =>\n Stream.fromEffect(sendOpenAiRequest(config, request, client)).pipe(\n Stream.flatMap(events => Stream.fromIterable(events))\n )\n })\n })\n )\n\nexport const OpenAiProviderLayer = Layer.effect(\n LLMProvider,\n Effect.gen(function* () {\n const config = yield* OpenAiConfig\n const client = yield* HttpClient.HttpClient\n\n return LLMProvider.of({\n stream: request =>\n Stream.fromEffect(sendOpenAiRequest(config, request, client)).pipe(\n Stream.flatMap(events => Stream.fromIterable(events))\n )\n })\n })\n).pipe(Layer.provide(Layer.mergeAll(OpenAiConfigLayer, FetchHttpClient.layer)))\n"],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AgentMessage } from "@yolk-sdk/agent/protocol";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { LLMError } from "@yolk-sdk/agent/loop";
|
|
4
|
+
|
|
5
|
+
//#region src/providers/transcript.d.ts
|
|
6
|
+
declare const validateProviderTranscript: (messages: ReadonlyArray<AgentMessage>) => Effect.Effect<void, LLMError>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { validateProviderTranscript };
|
|
9
|
+
//# sourceMappingURL=transcript.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript.d.mts","names":[],"sources":["../../src/providers/transcript.ts"],"mappings":";;;;;cAOa,0BAAA,GACX,QAAA,EAAU,aAAA,CAAc,YAAA,MACvB,MAAA,CAAO,MAAA,OAAa,QAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { validateNoDanglingHostToolCalls } from "@yolk-sdk/agent/protocol";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { LLMError } from "@yolk-sdk/agent/loop";
|
|
4
|
+
//#region src/providers/transcript.ts
|
|
5
|
+
const validateProviderTranscript = (messages) => {
|
|
6
|
+
const validation = validateNoDanglingHostToolCalls(messages);
|
|
7
|
+
if (validation._tag === "Valid") return Effect.void;
|
|
8
|
+
return Effect.fail(new LLMError({
|
|
9
|
+
cause: "validation_error",
|
|
10
|
+
message: validation.message,
|
|
11
|
+
retryable: false
|
|
12
|
+
}));
|
|
13
|
+
};
|
|
14
|
+
//#endregion
|
|
15
|
+
export { validateProviderTranscript };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=transcript.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript.mjs","names":[],"sources":["../../src/providers/transcript.ts"],"sourcesContent":["import { Effect } from 'effect'\nimport {\n validateNoDanglingHostToolCalls,\n type AgentMessage\n} from '@yolk-sdk/agent/protocol'\nimport { LLMError } from '@yolk-sdk/agent/loop'\n\nexport const validateProviderTranscript = (\n messages: ReadonlyArray<AgentMessage>\n): Effect.Effect<void, LLMError> => {\n const validation = validateNoDanglingHostToolCalls(messages)\n\n if (validation._tag === 'Valid') {\n return Effect.void\n }\n\n return Effect.fail(\n new LLMError({\n cause: 'validation_error',\n message: validation.message,\n retryable: false\n })\n )\n}\n"],"mappings":";;;;AAOA,MAAa,8BACX,aACkC;CAClC,MAAM,aAAa,gCAAgC,QAAQ;CAE3D,IAAI,WAAW,SAAS,SACtB,OAAO,OAAO;CAGhB,OAAO,OAAO,KACZ,IAAI,SAAS;EACX,OAAO;EACP,SAAS,WAAW;EACpB,WAAW;CACb,CAAC,CACH;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yolk-sdk/agent",
|
|
3
|
-
"version": "0.1.0-canary.
|
|
3
|
+
"version": "0.1.0-canary.23",
|
|
4
4
|
"description": "Protocol, loop, runtime, client, compaction, tools, React, providers, OAuth, skillset, and voice primitives for building Yolk agents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/loop/run.ts
CHANGED
|
@@ -173,6 +173,52 @@ const subagentCompletedEvent = (input: {
|
|
|
173
173
|
})
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
const toolCompletionEvents = (input: {
|
|
177
|
+
readonly call: ToolCall
|
|
178
|
+
readonly result: ToolResult
|
|
179
|
+
readonly model: string
|
|
180
|
+
readonly startedAtMs: number
|
|
181
|
+
readonly endedAtMs: number
|
|
182
|
+
}): ReadonlyArray<AgentEvent> => {
|
|
183
|
+
const completed = subagentCompletedEvent(input)
|
|
184
|
+
const toolCompleted = ToolExecutionCompleted.make({
|
|
185
|
+
call: input.call,
|
|
186
|
+
result: input.result,
|
|
187
|
+
createdAtMs: input.endedAtMs
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
return completed === undefined ? [toolCompleted] : [toolCompleted, completed]
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const toolErrorResult = (call: ToolCall, error: ToolError) =>
|
|
194
|
+
ToolResult.make({
|
|
195
|
+
toolCallId: call.id,
|
|
196
|
+
content: error.message,
|
|
197
|
+
isError: true
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
const toolErrorEvents = (input: {
|
|
201
|
+
readonly call: ToolCall
|
|
202
|
+
readonly error: ToolError
|
|
203
|
+
readonly model: string
|
|
204
|
+
readonly startedAtMs: number
|
|
205
|
+
readonly endedAtMs: number
|
|
206
|
+
}): ReadonlyArray<AgentEvent> => [
|
|
207
|
+
ToolExecutionError.make({
|
|
208
|
+
call: input.call,
|
|
209
|
+
message: input.error.message,
|
|
210
|
+
code: toolErrorCode(input.error),
|
|
211
|
+
createdAtMs: input.endedAtMs
|
|
212
|
+
}),
|
|
213
|
+
...toolCompletionEvents({
|
|
214
|
+
call: input.call,
|
|
215
|
+
result: toolErrorResult(input.call, input.error),
|
|
216
|
+
model: input.model,
|
|
217
|
+
startedAtMs: input.startedAtMs,
|
|
218
|
+
endedAtMs: input.endedAtMs
|
|
219
|
+
})
|
|
220
|
+
]
|
|
221
|
+
|
|
176
222
|
const unsupportedInputError = (message: string) =>
|
|
177
223
|
new LLMError({
|
|
178
224
|
cause: 'validation_error',
|
|
@@ -400,22 +446,15 @@ const makeToolExecutionStream = (
|
|
|
400
446
|
executor.execute(call).pipe(
|
|
401
447
|
Effect.flatMap(result =>
|
|
402
448
|
Clock.currentTimeMillis.pipe(
|
|
403
|
-
Effect.map(endedAtMs =>
|
|
404
|
-
|
|
449
|
+
Effect.map(endedAtMs =>
|
|
450
|
+
toolCompletionEvents({
|
|
405
451
|
call,
|
|
406
452
|
result,
|
|
407
453
|
model,
|
|
408
454
|
startedAtMs,
|
|
409
455
|
endedAtMs
|
|
410
456
|
})
|
|
411
|
-
|
|
412
|
-
call,
|
|
413
|
-
result,
|
|
414
|
-
createdAtMs: endedAtMs
|
|
415
|
-
})
|
|
416
|
-
|
|
417
|
-
return completed === undefined ? [toolCompleted] : [toolCompleted, completed]
|
|
418
|
-
})
|
|
457
|
+
)
|
|
419
458
|
)
|
|
420
459
|
)
|
|
421
460
|
)
|
|
@@ -424,16 +463,16 @@ const makeToolExecutionStream = (
|
|
|
424
463
|
Stream.catchTag('ToolError', error =>
|
|
425
464
|
Stream.fromEffect(Clock.currentTimeMillis).pipe(
|
|
426
465
|
Stream.flatMap(endedAtMs =>
|
|
427
|
-
Stream.
|
|
428
|
-
|
|
466
|
+
Stream.fromIterable(
|
|
467
|
+
toolErrorEvents({
|
|
429
468
|
call,
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
469
|
+
error,
|
|
470
|
+
model,
|
|
471
|
+
startedAtMs,
|
|
472
|
+
endedAtMs
|
|
433
473
|
})
|
|
434
474
|
)
|
|
435
|
-
)
|
|
436
|
-
Stream.concat(Stream.fail(error))
|
|
475
|
+
)
|
|
437
476
|
)
|
|
438
477
|
)
|
|
439
478
|
)
|
package/src/protocol/content.ts
CHANGED
|
@@ -201,6 +201,17 @@ export const attachmentSourceDataUrl = (source: AttachmentSource, mimeType: stri
|
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
export const attachmentSourceUrl = (source: AttachmentSource, mimeType: string) => {
|
|
205
|
+
switch (source._tag) {
|
|
206
|
+
case 'InlineBase64':
|
|
207
|
+
return Option.some(`data:${mimeType};base64,${source.data}`)
|
|
208
|
+
case 'Url':
|
|
209
|
+
return Option.some(source.url)
|
|
210
|
+
case 'Ref':
|
|
211
|
+
return Option.none<string>()
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
204
215
|
const normalizeMimeType = (mimeType: string) => mimeType.split(';', 1)[0]?.trim().toLowerCase() ?? ''
|
|
205
216
|
|
|
206
217
|
const textDocumentMimeTypeByExtension: Readonly<Record<string, string>> = {
|
package/src/protocol/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ export {
|
|
|
11
11
|
attachmentSourceDataUrl,
|
|
12
12
|
AttachmentSource,
|
|
13
13
|
attachmentSourceText,
|
|
14
|
+
attachmentSourceUrl,
|
|
14
15
|
type AttachmentContentPart,
|
|
15
16
|
type AttachmentSourceResolver,
|
|
16
17
|
attachmentSourcePreview,
|
|
@@ -96,8 +97,16 @@ export {
|
|
|
96
97
|
assistantContent,
|
|
97
98
|
assistantHostToolCalls,
|
|
98
99
|
assistantReasoningText,
|
|
100
|
+
danglingHostToolCalls,
|
|
99
101
|
messageContextText,
|
|
100
|
-
prependMessageContextToContent
|
|
102
|
+
prependMessageContextToContent,
|
|
103
|
+
repairDanglingHostToolCalls,
|
|
104
|
+
validateNoDanglingHostToolCalls
|
|
105
|
+
} from './message.ts'
|
|
106
|
+
export type {
|
|
107
|
+
DanglingHostToolCall,
|
|
108
|
+
RepairDanglingHostToolCallsOptions,
|
|
109
|
+
TranscriptInvariantValidation
|
|
101
110
|
} from './message.ts'
|
|
102
111
|
export { AgentReasoningEffort } from './reasoning.ts'
|
|
103
112
|
export {
|
package/src/protocol/message.ts
CHANGED
|
@@ -87,6 +87,25 @@ export class ToolResultMessage extends Schema.TaggedClass<ToolResultMessage>()('
|
|
|
87
87
|
export const AgentMessage = Schema.Union([UserMessage, AssistantAgentMessage, ToolResultMessage])
|
|
88
88
|
export type AgentMessage = typeof AgentMessage.Type
|
|
89
89
|
|
|
90
|
+
export type DanglingHostToolCall = {
|
|
91
|
+
readonly call: ToolCall
|
|
92
|
+
readonly assistantMessageIndex: number
|
|
93
|
+
readonly beforeMessageIndex?: number
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type TranscriptInvariantValidation =
|
|
97
|
+
| { readonly _tag: 'Valid' }
|
|
98
|
+
| {
|
|
99
|
+
readonly _tag: 'DanglingHostToolCalls'
|
|
100
|
+
readonly calls: ReadonlyArray<DanglingHostToolCall>
|
|
101
|
+
readonly message: string
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type RepairDanglingHostToolCallsOptions = {
|
|
105
|
+
readonly content?: (call: ToolCall) => Content
|
|
106
|
+
readonly structuredContent?: (call: ToolCall) => unknown
|
|
107
|
+
}
|
|
108
|
+
|
|
90
109
|
export const assistantContent = (message: AssistantAgentMessage): Content => {
|
|
91
110
|
const parts = message.parts.flatMap(part => (part._tag === 'Text' ? [part.content] : []))
|
|
92
111
|
const first = parts[0]
|
|
@@ -108,6 +127,114 @@ export const assistantReasoningText = (message: AssistantAgentMessage) =>
|
|
|
108
127
|
export const assistantHostToolCalls = (message: AssistantAgentMessage) =>
|
|
109
128
|
message.parts.flatMap(part => (part._tag === 'HostToolCall' ? [part.call] : []))
|
|
110
129
|
|
|
130
|
+
type PendingHostToolCall = {
|
|
131
|
+
readonly call: ToolCall
|
|
132
|
+
readonly assistantMessageIndex: number
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const pendingHostToolCalls = (message: AgentMessage, messageIndex: number) =>
|
|
136
|
+
message._tag === 'Assistant'
|
|
137
|
+
? assistantHostToolCalls(message).map(call => ({ call, assistantMessageIndex: messageIndex }))
|
|
138
|
+
: []
|
|
139
|
+
|
|
140
|
+
const danglingHostToolCall = (
|
|
141
|
+
pending: PendingHostToolCall,
|
|
142
|
+
beforeMessageIndex: number | undefined
|
|
143
|
+
): DanglingHostToolCall => ({
|
|
144
|
+
call: pending.call,
|
|
145
|
+
assistantMessageIndex: pending.assistantMessageIndex,
|
|
146
|
+
...(beforeMessageIndex === undefined ? {} : { beforeMessageIndex })
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
const danglingHostToolCallSummary = (calls: ReadonlyArray<DanglingHostToolCall>) =>
|
|
150
|
+
calls.map(({ call }) => `${call.name} (${call.id})`).join(', ')
|
|
151
|
+
|
|
152
|
+
const danglingHostToolResultContent = (call: ToolCall) =>
|
|
153
|
+
`Tool ${call.name} did not return a result before the transcript continued.`
|
|
154
|
+
|
|
155
|
+
const danglingHostToolResultMessage = (
|
|
156
|
+
call: ToolCall,
|
|
157
|
+
options: RepairDanglingHostToolCallsOptions | undefined
|
|
158
|
+
) => {
|
|
159
|
+
const structuredContent = options?.structuredContent?.(call)
|
|
160
|
+
|
|
161
|
+
return ToolResultMessage.make({
|
|
162
|
+
toolCallId: call.id,
|
|
163
|
+
content: options?.content?.(call) ?? danglingHostToolResultContent(call),
|
|
164
|
+
isError: true,
|
|
165
|
+
...(structuredContent === undefined ? {} : { structuredContent })
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export const danglingHostToolCalls = (
|
|
170
|
+
messages: ReadonlyArray<AgentMessage>
|
|
171
|
+
): ReadonlyArray<DanglingHostToolCall> => {
|
|
172
|
+
const dangling: Array<DanglingHostToolCall> = []
|
|
173
|
+
let pending: ReadonlyArray<PendingHostToolCall> = []
|
|
174
|
+
|
|
175
|
+
for (const [messageIndex, message] of messages.entries()) {
|
|
176
|
+
if (message._tag !== 'ToolResult' && pending.length > 0) {
|
|
177
|
+
dangling.push(...pending.map(call => danglingHostToolCall(call, messageIndex)))
|
|
178
|
+
pending = []
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
pending = [...pending, ...pendingHostToolCalls(message, messageIndex)]
|
|
182
|
+
|
|
183
|
+
if (message._tag === 'ToolResult') {
|
|
184
|
+
pending = pending.filter(call => call.call.id !== message.toolCallId)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
dangling.push(...pending.map(call => danglingHostToolCall(call, undefined)))
|
|
189
|
+
|
|
190
|
+
return dangling
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export const validateNoDanglingHostToolCalls = (
|
|
194
|
+
messages: ReadonlyArray<AgentMessage>
|
|
195
|
+
): TranscriptInvariantValidation => {
|
|
196
|
+
const dangling = danglingHostToolCalls(messages)
|
|
197
|
+
|
|
198
|
+
if (dangling.length === 0) {
|
|
199
|
+
return { _tag: 'Valid' }
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
_tag: 'DanglingHostToolCalls',
|
|
204
|
+
calls: dangling,
|
|
205
|
+
message: `Transcript has host tool calls without tool results: ${danglingHostToolCallSummary(dangling)}`
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export const repairDanglingHostToolCalls = (
|
|
210
|
+
messages: ReadonlyArray<AgentMessage>,
|
|
211
|
+
options?: RepairDanglingHostToolCallsOptions
|
|
212
|
+
): ReadonlyArray<AgentMessage> => {
|
|
213
|
+
const repaired: Array<AgentMessage> = []
|
|
214
|
+
let pending: ReadonlyArray<ToolCall> = []
|
|
215
|
+
|
|
216
|
+
for (const message of messages) {
|
|
217
|
+
if (message._tag !== 'ToolResult' && pending.length > 0) {
|
|
218
|
+
repaired.push(...pending.map(call => danglingHostToolResultMessage(call, options)))
|
|
219
|
+
pending = []
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
repaired.push(message)
|
|
223
|
+
|
|
224
|
+
if (message._tag === 'Assistant') {
|
|
225
|
+
pending = [...pending, ...assistantHostToolCalls(message)]
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (message._tag === 'ToolResult') {
|
|
229
|
+
pending = pending.filter(call => call.id !== message.toolCallId)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
repaired.push(...pending.map(call => danglingHostToolResultMessage(call, options)))
|
|
234
|
+
|
|
235
|
+
return repaired
|
|
236
|
+
}
|
|
237
|
+
|
|
111
238
|
const formatCreatedAtMs = (createdAtMs: number) =>
|
|
112
239
|
Number.isFinite(createdAtMs) ? new Date(createdAtMs).toISOString() : String(createdAtMs)
|
|
113
240
|
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
AgentOutputUsage,
|
|
12
12
|
AgentUsage,
|
|
13
13
|
ToolCall,
|
|
14
|
-
attachmentSourceBase64,
|
|
15
14
|
attachmentSourceText,
|
|
16
15
|
assistantContent,
|
|
17
16
|
assistantHostToolCalls,
|
|
@@ -48,6 +47,7 @@ import {
|
|
|
48
47
|
providerFailureCause,
|
|
49
48
|
providerFailureRetryable
|
|
50
49
|
} from '../provider-error.ts'
|
|
50
|
+
import { validateProviderTranscript } from '../transcript.ts'
|
|
51
51
|
|
|
52
52
|
export type AnthropicClaudeProviderConfig = {
|
|
53
53
|
readonly token: OAuthAccessToken
|
|
@@ -65,20 +65,30 @@ type AnthropicSystemBlock = AnthropicTextBlock
|
|
|
65
65
|
|
|
66
66
|
type AnthropicImageBlock = {
|
|
67
67
|
readonly type: 'image'
|
|
68
|
-
readonly source:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
readonly source:
|
|
69
|
+
| {
|
|
70
|
+
readonly type: 'base64'
|
|
71
|
+
readonly media_type: string
|
|
72
|
+
readonly data: string
|
|
73
|
+
}
|
|
74
|
+
| {
|
|
75
|
+
readonly type: 'url'
|
|
76
|
+
readonly url: string
|
|
77
|
+
}
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
type AnthropicDocumentBlock = {
|
|
76
81
|
readonly type: 'document'
|
|
77
|
-
readonly source:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
readonly source:
|
|
83
|
+
| {
|
|
84
|
+
readonly type: 'base64'
|
|
85
|
+
readonly media_type: 'application/pdf'
|
|
86
|
+
readonly data: string
|
|
87
|
+
}
|
|
88
|
+
| {
|
|
89
|
+
readonly type: 'url'
|
|
90
|
+
readonly url: string
|
|
91
|
+
}
|
|
82
92
|
readonly title?: string
|
|
83
93
|
}
|
|
84
94
|
|
|
@@ -442,6 +452,60 @@ const unsupportedContentError = (contentType: string) =>
|
|
|
442
452
|
retryable: false
|
|
443
453
|
})
|
|
444
454
|
|
|
455
|
+
const imageToAnthropicBlock = (
|
|
456
|
+
part: Extract<ContentPart, { readonly _tag: 'Image' }>
|
|
457
|
+
): Effect.Effect<AnthropicImageBlock, LLMError> => {
|
|
458
|
+
switch (part.source._tag) {
|
|
459
|
+
case 'InlineBase64':
|
|
460
|
+
return Effect.succeed({
|
|
461
|
+
type: 'image',
|
|
462
|
+
source: {
|
|
463
|
+
type: 'base64',
|
|
464
|
+
media_type: part.mimeType,
|
|
465
|
+
data: part.source.data
|
|
466
|
+
}
|
|
467
|
+
})
|
|
468
|
+
case 'Url':
|
|
469
|
+
return Effect.succeed({
|
|
470
|
+
type: 'image',
|
|
471
|
+
source: {
|
|
472
|
+
type: 'url',
|
|
473
|
+
url: part.source.url
|
|
474
|
+
}
|
|
475
|
+
})
|
|
476
|
+
case 'Ref':
|
|
477
|
+
return Effect.fail(unsupportedContentError('Unresolved image source'))
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const pdfDocumentToAnthropicBlock = (
|
|
482
|
+
part: Extract<ContentPart, { readonly _tag: 'Document' }>
|
|
483
|
+
): Effect.Effect<AnthropicDocumentBlock, LLMError> => {
|
|
484
|
+
switch (part.source._tag) {
|
|
485
|
+
case 'InlineBase64':
|
|
486
|
+
return Effect.succeed({
|
|
487
|
+
type: 'document',
|
|
488
|
+
source: {
|
|
489
|
+
type: 'base64',
|
|
490
|
+
media_type: 'application/pdf',
|
|
491
|
+
data: part.source.data
|
|
492
|
+
},
|
|
493
|
+
title: part.title ?? part.filename
|
|
494
|
+
})
|
|
495
|
+
case 'Url':
|
|
496
|
+
return Effect.succeed({
|
|
497
|
+
type: 'document',
|
|
498
|
+
source: {
|
|
499
|
+
type: 'url',
|
|
500
|
+
url: part.source.url
|
|
501
|
+
},
|
|
502
|
+
title: part.title ?? part.filename
|
|
503
|
+
})
|
|
504
|
+
case 'Ref':
|
|
505
|
+
return Effect.fail(unsupportedContentError('Unresolved document source'))
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
445
509
|
const textDocumentToAnthropicBlock = (part: Extract<ContentPart, { readonly _tag: 'Document' }>) =>
|
|
446
510
|
attachmentSourceText(part.source).pipe(
|
|
447
511
|
Effect.mapError(() => unsupportedContentError('Invalid document text')),
|
|
@@ -465,33 +529,12 @@ const contentPartToUserBlock = (part: ContentPart): Effect.Effect<AnthropicUserB
|
|
|
465
529
|
case 'Text':
|
|
466
530
|
return Effect.succeed({ type: 'text', text: part.text })
|
|
467
531
|
case 'Image':
|
|
468
|
-
return
|
|
469
|
-
onNone: () => Effect.fail(unsupportedContentError('Unresolved image source')),
|
|
470
|
-
onSome: data => Effect.succeed({
|
|
471
|
-
type: 'image',
|
|
472
|
-
source: {
|
|
473
|
-
type: 'base64',
|
|
474
|
-
media_type: part.mimeType,
|
|
475
|
-
data
|
|
476
|
-
}
|
|
477
|
-
})
|
|
478
|
-
})
|
|
532
|
+
return imageToAnthropicBlock(part)
|
|
479
533
|
case 'Document':
|
|
480
534
|
return isTextDocumentMimeType(part.mimeType)
|
|
481
535
|
? textDocumentToAnthropicBlock(part)
|
|
482
536
|
: part.mimeType === 'application/pdf'
|
|
483
|
-
?
|
|
484
|
-
onNone: () => Effect.fail(unsupportedContentError('Unresolved document source')),
|
|
485
|
-
onSome: data => Effect.succeed({
|
|
486
|
-
type: 'document',
|
|
487
|
-
source: {
|
|
488
|
-
type: 'base64',
|
|
489
|
-
media_type: 'application/pdf',
|
|
490
|
-
data
|
|
491
|
-
},
|
|
492
|
-
title: part.title ?? part.filename
|
|
493
|
-
})
|
|
494
|
-
})
|
|
537
|
+
? pdfDocumentToAnthropicBlock(part)
|
|
495
538
|
: Effect.fail(unsupportedContentError(`Document ${part.mimeType}`))
|
|
496
539
|
case 'Audio':
|
|
497
540
|
return Effect.fail(unsupportedContentError('Audio'))
|
|
@@ -802,6 +845,7 @@ export const toAnthropicClaudeRequestBody = (
|
|
|
802
845
|
config?: { readonly maxTokens?: number; readonly stream?: boolean }
|
|
803
846
|
): Effect.Effect<AnthropicRequestBody, LLMError> =>
|
|
804
847
|
Effect.gen(function* () {
|
|
848
|
+
yield* validateProviderTranscript(request.messages)
|
|
805
849
|
const rawMessages = yield* Effect.forEach(request.messages, toAnthropicMessage)
|
|
806
850
|
const billingSystemBlock = yield* makeAnthropicClaudeBillingSystemBlock(rawMessages)
|
|
807
851
|
const messages = prependSystemPromptToFirstUserMessage(rawMessages, request.systemPrompt)
|