@acentric/llm 0.0.0-stage → 0.1.0

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.
Files changed (232) hide show
  1. package/README.md +128 -2
  2. package/dist/catalog/anthropic.d.ts +77 -0
  3. package/dist/catalog/anthropic.d.ts.map +1 -0
  4. package/dist/catalog/anthropic.js +73 -0
  5. package/dist/catalog/anthropic.js.map +1 -0
  6. package/dist/catalog/fireworks.d.ts +93 -0
  7. package/dist/catalog/fireworks.d.ts.map +1 -0
  8. package/dist/catalog/fireworks.js +117 -0
  9. package/dist/catalog/fireworks.js.map +1 -0
  10. package/dist/catalog/openai.d.ts +103 -0
  11. package/dist/catalog/openai.d.ts.map +1 -0
  12. package/dist/catalog/openai.js +102 -0
  13. package/dist/catalog/openai.js.map +1 -0
  14. package/dist/client/anthropic/errors.d.ts +4 -0
  15. package/dist/client/anthropic/errors.d.ts.map +1 -0
  16. package/dist/client/anthropic/errors.js +80 -0
  17. package/dist/client/anthropic/errors.js.map +1 -0
  18. package/dist/client/anthropic/request.d.ts +9 -0
  19. package/dist/client/anthropic/request.d.ts.map +1 -0
  20. package/dist/client/anthropic/request.js +232 -0
  21. package/dist/client/anthropic/request.js.map +1 -0
  22. package/dist/client/anthropic/response.d.ts +13 -0
  23. package/dist/client/anthropic/response.d.ts.map +1 -0
  24. package/dist/client/anthropic/response.js +207 -0
  25. package/dist/client/anthropic/response.js.map +1 -0
  26. package/dist/client/direct.d.ts +18 -0
  27. package/dist/client/direct.d.ts.map +1 -0
  28. package/dist/client/direct.js +104 -0
  29. package/dist/client/direct.js.map +1 -0
  30. package/dist/client/gateway.d.ts +43 -0
  31. package/dist/client/gateway.d.ts.map +1 -0
  32. package/dist/client/gateway.js +72 -0
  33. package/dist/client/gateway.js.map +1 -0
  34. package/dist/client/http.d.ts +41 -0
  35. package/dist/client/http.d.ts.map +1 -0
  36. package/dist/client/http.js +190 -0
  37. package/dist/client/http.js.map +1 -0
  38. package/dist/client/llm.d.ts +41 -0
  39. package/dist/client/llm.d.ts.map +1 -0
  40. package/dist/client/llm.js +100 -0
  41. package/dist/client/llm.js.map +1 -0
  42. package/dist/client/responses/errors.d.ts +13 -0
  43. package/dist/client/responses/errors.d.ts.map +1 -0
  44. package/dist/client/responses/errors.js +144 -0
  45. package/dist/client/responses/errors.js.map +1 -0
  46. package/dist/client/responses/profile.d.ts +50 -0
  47. package/dist/client/responses/profile.d.ts.map +1 -0
  48. package/dist/client/responses/profile.js +4 -0
  49. package/dist/client/responses/profile.js.map +1 -0
  50. package/dist/client/responses/providers.d.ts +4 -0
  51. package/dist/client/responses/providers.d.ts.map +1 -0
  52. package/dist/client/responses/providers.js +75 -0
  53. package/dist/client/responses/providers.js.map +1 -0
  54. package/dist/client/responses/request.d.ts +5 -0
  55. package/dist/client/responses/request.d.ts.map +1 -0
  56. package/dist/client/responses/request.js +248 -0
  57. package/dist/client/responses/request.js.map +1 -0
  58. package/dist/client/responses/response.d.ts +10 -0
  59. package/dist/client/responses/response.d.ts.map +1 -0
  60. package/dist/client/responses/response.js +176 -0
  61. package/dist/client/responses/response.js.map +1 -0
  62. package/dist/client/util.d.ts +21 -0
  63. package/dist/client/util.d.ts.map +1 -0
  64. package/dist/client/util.js +63 -0
  65. package/dist/client/util.js.map +1 -0
  66. package/dist/client.d.ts +9 -0
  67. package/dist/client.d.ts.map +1 -0
  68. package/dist/client.js +10 -0
  69. package/dist/client.js.map +1 -0
  70. package/dist/content.d.ts +38 -0
  71. package/dist/content.d.ts.map +1 -0
  72. package/dist/content.js +5 -0
  73. package/dist/content.js.map +1 -0
  74. package/dist/convert/anthropic.d.ts +3 -0
  75. package/dist/convert/anthropic.d.ts.map +1 -0
  76. package/dist/convert/anthropic.js +114 -0
  77. package/dist/convert/anthropic.js.map +1 -0
  78. package/dist/convert/index.d.ts +12 -0
  79. package/dist/convert/index.d.ts.map +1 -0
  80. package/dist/convert/index.js +221 -0
  81. package/dist/convert/index.js.map +1 -0
  82. package/dist/convert/responses.d.ts +3 -0
  83. package/dist/convert/responses.d.ts.map +1 -0
  84. package/dist/convert/responses.js +197 -0
  85. package/dist/convert/responses.js.map +1 -0
  86. package/dist/convert/types.d.ts +88 -0
  87. package/dist/convert/types.d.ts.map +1 -0
  88. package/dist/convert/types.js +3 -0
  89. package/dist/convert/types.js.map +1 -0
  90. package/dist/cost.d.ts +18 -0
  91. package/dist/cost.d.ts.map +1 -0
  92. package/dist/cost.js +49 -0
  93. package/dist/cost.js.map +1 -0
  94. package/dist/failure.d.ts +90 -0
  95. package/dist/failure.d.ts.map +1 -0
  96. package/dist/failure.js +111 -0
  97. package/dist/failure.js.map +1 -0
  98. package/dist/fields.d.ts +14 -0
  99. package/dist/fields.d.ts.map +1 -0
  100. package/dist/fields.js +3 -0
  101. package/dist/fields.js.map +1 -0
  102. package/dist/formats/anthropic.d.ts +152 -0
  103. package/dist/formats/anthropic.d.ts.map +1 -0
  104. package/dist/formats/anthropic.js +7 -0
  105. package/dist/formats/anthropic.js.map +1 -0
  106. package/dist/formats/responses.d.ts +179 -0
  107. package/dist/formats/responses.d.ts.map +1 -0
  108. package/dist/formats/responses.js +9 -0
  109. package/dist/formats/responses.js.map +1 -0
  110. package/dist/index.d.ts +26 -0
  111. package/dist/index.d.ts.map +1 -0
  112. package/dist/index.js +11 -0
  113. package/dist/index.js.map +1 -0
  114. package/dist/json.d.ts +6 -0
  115. package/dist/json.d.ts.map +1 -0
  116. package/dist/json.js +10 -0
  117. package/dist/json.js.map +1 -0
  118. package/dist/messages.d.ts +82 -0
  119. package/dist/messages.d.ts.map +1 -0
  120. package/dist/messages.js +4 -0
  121. package/dist/messages.js.map +1 -0
  122. package/dist/model.d.ts +36 -0
  123. package/dist/model.d.ts.map +1 -0
  124. package/dist/model.js +4 -0
  125. package/dist/model.js.map +1 -0
  126. package/dist/models.d.ts +9 -0
  127. package/dist/models.d.ts.map +1 -0
  128. package/dist/models.js +19 -0
  129. package/dist/models.js.map +1 -0
  130. package/dist/providers/anthropic.d.ts +130 -0
  131. package/dist/providers/anthropic.d.ts.map +1 -0
  132. package/dist/providers/anthropic.js +5 -0
  133. package/dist/providers/anthropic.js.map +1 -0
  134. package/dist/providers/fireworks.d.ts +108 -0
  135. package/dist/providers/fireworks.d.ts.map +1 -0
  136. package/dist/providers/fireworks.js +6 -0
  137. package/dist/providers/fireworks.js.map +1 -0
  138. package/dist/providers/openai.d.ts +193 -0
  139. package/dist/providers/openai.d.ts.map +1 -0
  140. package/dist/providers/openai.js +4 -0
  141. package/dist/providers/openai.js.map +1 -0
  142. package/dist/providers.d.ts +50 -0
  143. package/dist/providers.d.ts.map +1 -0
  144. package/dist/providers.js +5 -0
  145. package/dist/providers.js.map +1 -0
  146. package/dist/read/anthropic.d.ts +15 -0
  147. package/dist/read/anthropic.d.ts.map +1 -0
  148. package/dist/read/anthropic.js +56 -0
  149. package/dist/read/anthropic.js.map +1 -0
  150. package/dist/read/responses.d.ts +26 -0
  151. package/dist/read/responses.d.ts.map +1 -0
  152. package/dist/read/responses.js +121 -0
  153. package/dist/read/responses.js.map +1 -0
  154. package/dist/read.d.ts +32 -0
  155. package/dist/read.d.ts.map +1 -0
  156. package/dist/read.js +73 -0
  157. package/dist/read.js.map +1 -0
  158. package/dist/request.d.ts +23 -0
  159. package/dist/request.d.ts.map +1 -0
  160. package/dist/request.js +4 -0
  161. package/dist/request.js.map +1 -0
  162. package/dist/response.d.ts +38 -0
  163. package/dist/response.d.ts.map +1 -0
  164. package/dist/response.js +4 -0
  165. package/dist/response.js.map +1 -0
  166. package/dist/secrets.d.ts +22 -0
  167. package/dist/secrets.d.ts.map +1 -0
  168. package/dist/secrets.js +35 -0
  169. package/dist/secrets.js.map +1 -0
  170. package/dist/tool-calls.d.ts +28 -0
  171. package/dist/tool-calls.d.ts.map +1 -0
  172. package/dist/tool-calls.js +4 -0
  173. package/dist/tool-calls.js.map +1 -0
  174. package/dist/tools.d.ts +33 -0
  175. package/dist/tools.d.ts.map +1 -0
  176. package/dist/tools.js +4 -0
  177. package/dist/tools.js.map +1 -0
  178. package/dist/usage.d.ts +31 -0
  179. package/dist/usage.d.ts.map +1 -0
  180. package/dist/usage.js +3 -0
  181. package/dist/usage.js.map +1 -0
  182. package/dist/version.d.ts +2 -0
  183. package/dist/version.d.ts.map +1 -0
  184. package/dist/version.js +6 -0
  185. package/dist/version.js.map +1 -0
  186. package/package.json +33 -3
  187. package/src/catalog/anthropic.ts +83 -0
  188. package/src/catalog/fireworks.ts +121 -0
  189. package/src/catalog/openai.ts +133 -0
  190. package/src/client/anthropic/errors.ts +88 -0
  191. package/src/client/anthropic/request.ts +272 -0
  192. package/src/client/anthropic/response.ts +236 -0
  193. package/src/client/direct.ts +157 -0
  194. package/src/client/gateway.ts +120 -0
  195. package/src/client/http.ts +231 -0
  196. package/src/client/llm.ts +151 -0
  197. package/src/client/responses/errors.ts +166 -0
  198. package/src/client/responses/profile.ts +55 -0
  199. package/src/client/responses/providers.ts +87 -0
  200. package/src/client/responses/request.ts +326 -0
  201. package/src/client/responses/response.ts +203 -0
  202. package/src/client/util.ts +81 -0
  203. package/src/client.ts +20 -0
  204. package/src/content.ts +46 -0
  205. package/src/convert/anthropic.ts +115 -0
  206. package/src/convert/index.ts +275 -0
  207. package/src/convert/responses.ts +203 -0
  208. package/src/convert/types.ts +104 -0
  209. package/src/cost.ts +71 -0
  210. package/src/failure.ts +164 -0
  211. package/src/fields.ts +13 -0
  212. package/src/formats/anthropic.ts +191 -0
  213. package/src/formats/responses.ts +207 -0
  214. package/src/index.ts +44 -0
  215. package/src/json.ts +13 -0
  216. package/src/messages.ts +99 -0
  217. package/src/model.ts +41 -0
  218. package/src/models.ts +23 -0
  219. package/src/providers/anthropic.ts +137 -0
  220. package/src/providers/fireworks.ts +140 -0
  221. package/src/providers/openai.ts +275 -0
  222. package/src/providers.ts +59 -0
  223. package/src/read/anthropic.ts +64 -0
  224. package/src/read/responses.ts +159 -0
  225. package/src/read.ts +108 -0
  226. package/src/request.ts +28 -0
  227. package/src/response.ts +44 -0
  228. package/src/secrets.ts +40 -0
  229. package/src/tool-calls.ts +33 -0
  230. package/src/tools.ts +38 -0
  231. package/src/usage.ts +33 -0
  232. package/src/version.ts +6 -0
@@ -0,0 +1,159 @@
1
+ // Readers over a Responses assistant message (OpenAI, Fireworks). They never
2
+ // change the message, which is sent back as it is.
3
+
4
+ import type { Json, JsonObject } from '@acentric/types';
5
+
6
+ import { isArray, isObject } from '../json.js';
7
+
8
+ /** What a reader returns for one call, before it is typed for its provider. */
9
+ export type ReadCall = {
10
+ readonly toolCallId: string;
11
+ readonly toolName: string;
12
+ readonly toolType: 'function' | 'custom' | 'native';
13
+ readonly arguments?: string;
14
+ readonly input?: string;
15
+ readonly namespace?: string;
16
+ readonly async?: boolean;
17
+ readonly resultOptions?: JsonObject;
18
+ readonly itemType: string;
19
+ readonly item: JsonObject;
20
+ };
21
+
22
+ export type TextOptions = {
23
+ /** `final`, the default, leaves out commentary, which is a progress update and not the answer. */
24
+ readonly phase?: 'final' | 'all';
25
+ };
26
+
27
+ /** Item types answered with a `tool_result` message, and the tool type they answer. */
28
+ const RESULT_CALLS: { readonly [type: string]: 'function' | 'custom' } = {
29
+ function_call: 'function',
30
+ custom_tool_call: 'custom',
31
+ };
32
+
33
+ /** Provider-defined calls the caller runs and answers with a `native` message. */
34
+ const NATIVE_CALLS: ReadonlySet<string> = new Set([
35
+ 'computer_call',
36
+ 'shell_call',
37
+ 'apply_patch_call',
38
+ 'local_shell_call',
39
+ 'tool_search_call',
40
+ 'mcp_approval_request',
41
+ ]);
42
+
43
+ function messages(items: readonly Json[]): JsonObject[] {
44
+ return items.filter(
45
+ (item): item is JsonObject =>
46
+ isObject(item) && item['type'] === 'message' && isArray(item['content']),
47
+ );
48
+ }
49
+
50
+ function parts(message: JsonObject): JsonObject[] {
51
+ const content = message['content'];
52
+ return isArray(content) ? content.filter(isObject) : [];
53
+ }
54
+
55
+ export function outputText(items: readonly Json[], { phase = 'final' }: TextOptions = {}): string {
56
+ return messages(items)
57
+ .filter((message) => phase === 'all' || message['phase'] !== 'commentary')
58
+ .map((message) =>
59
+ parts(message)
60
+ .map((part) => (part['type'] === 'output_text' ? text(part['text']) : ''))
61
+ .join(''),
62
+ )
63
+ .filter((joined) => joined !== '')
64
+ .join('\n\n');
65
+ }
66
+
67
+ export function refusals(items: readonly Json[]): string[] {
68
+ return messages(items).flatMap((message) =>
69
+ parts(message).flatMap((part) => {
70
+ const refusal = part['refusal'];
71
+ return part['type'] === 'refusal' && typeof refusal === 'string' ? [refusal] : [];
72
+ }),
73
+ );
74
+ }
75
+
76
+ export function reasoningSummary(items: readonly Json[]): string[] {
77
+ return items.flatMap((item) => {
78
+ if (!isObject(item) || item['type'] !== 'reasoning') return [];
79
+ const summary = item['summary'];
80
+ if (!isArray(summary)) return [];
81
+ return summary.flatMap((part) =>
82
+ isObject(part) && typeof part['text'] === 'string' ? [part['text']] : [],
83
+ );
84
+ });
85
+ }
86
+
87
+ /** Every call the caller answers, answered within the message or not. */
88
+ export function toolCalls(items: readonly Json[]): ReadCall[] {
89
+ const calls: ReadCall[] = [];
90
+ for (const item of items) {
91
+ if (!isObject(item)) continue;
92
+ const type = item['type'];
93
+ if (typeof type !== 'string') continue;
94
+ const toolType = RESULT_CALLS[type] ?? (NATIVE_CALLS.has(type) ? 'native' : undefined);
95
+ if (toolType === undefined) continue;
96
+ // Tool search that runs at the provider has no call to answer.
97
+ if (type === 'tool_search_call' && item['execution'] !== 'client') continue;
98
+ const id = type === 'mcp_approval_request' ? item['id'] : item['call_id'];
99
+ if (typeof id !== 'string' || id === '') continue;
100
+ calls.push(toCall(item, type, id, toolType));
101
+ }
102
+ return calls;
103
+ }
104
+
105
+ /** Calls with no answer in the message itself, as a hosted shell call has in its output. */
106
+ export function pendingToolCalls(items: readonly Json[]): ReadCall[] {
107
+ const answered = new Set<string>();
108
+ for (const item of items) {
109
+ if (!isObject(item)) continue;
110
+ const type = item['type'];
111
+ const request = item['approval_request_id'];
112
+ const callId = item['call_id'];
113
+ if (type === 'mcp_approval_response' && typeof request === 'string') answered.add(request);
114
+ else if (typeof type === 'string' && type.endsWith('_output') && typeof callId === 'string')
115
+ answered.add(callId);
116
+ }
117
+ return toolCalls(items).filter((call) => !answered.has(call.toolCallId));
118
+ }
119
+
120
+ function toCall(
121
+ item: JsonObject,
122
+ type: string,
123
+ id: string,
124
+ toolType: ReadCall['toolType'],
125
+ ): ReadCall {
126
+ const name = item['name'];
127
+ const namespace = item['namespace'];
128
+ const serverLabel = item['server_label'];
129
+ const args = item['arguments'];
130
+ const input = item['input'];
131
+ const caller = item['caller'];
132
+ const callNamespace =
133
+ typeof namespace === 'string'
134
+ ? namespace
135
+ : type === 'mcp_approval_request' && typeof serverLabel === 'string'
136
+ ? serverLabel
137
+ : undefined;
138
+ // A result must repeat the call's caller (programmatic tool calling) and may name its namespace.
139
+ const resultOptions: JsonObject = {
140
+ ...(isObject(caller) ? { caller } : {}),
141
+ ...(toolType === 'function' && typeof namespace === 'string' ? { namespace } : {}),
142
+ };
143
+ return {
144
+ toolCallId: id,
145
+ toolName: typeof name === 'string' ? name : type.replace(/_call$/, ''),
146
+ toolType,
147
+ ...(typeof args === 'string' && toolType !== 'native' ? { arguments: args } : {}),
148
+ ...(typeof input === 'string' ? { input } : {}),
149
+ ...(callNamespace === undefined ? {} : { namespace: callNamespace }),
150
+ ...(item['async'] === true ? { async: true } : {}),
151
+ ...(Object.keys(resultOptions).length > 0 ? { resultOptions } : {}),
152
+ itemType: type,
153
+ item,
154
+ };
155
+ }
156
+
157
+ function text(value: Json | undefined): string {
158
+ return typeof value === 'string' ? value : '';
159
+ }
package/src/read.ts ADDED
@@ -0,0 +1,108 @@
1
+ // Readers over any provider's assistant message, and the helpers that build
2
+ // what a request sends. Each reader goes to its message's API format. Pure:
3
+ // a step can use them.
4
+
5
+ import type { Json } from '@acentric/types';
6
+
7
+ import type { ContentPart } from './content.js';
8
+ import type { AssistantMessage, Message, RequestMessage, ToolResultMessage } from './messages.js';
9
+ import type { Provider } from './providers.js';
10
+ import * as anthropic from './read/anthropic.js';
11
+ import * as responses from './read/responses.js';
12
+ import type { ReadCall, TextOptions } from './read/responses.js';
13
+ import type { ToolCall } from './tool-calls.js';
14
+
15
+ export type { TextOptions } from './read/responses.js';
16
+
17
+ /** The message's items, read as plain Json. */
18
+ function items(message: AssistantMessage): readonly Json[] {
19
+ return message.content;
20
+ }
21
+
22
+ /** Typed for the message's provider: a reader returns that message's own items. */
23
+ function typed<P extends Provider>(calls: readonly ReadCall[]): ToolCall<P>[] {
24
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
25
+ return calls as unknown as ToolCall<P>[];
26
+ }
27
+
28
+ /**
29
+ * The text of the message's answer. On the Responses format, messages are
30
+ * joined by a blank line and commentary is left out unless `{ phase: 'all' }`.
31
+ */
32
+ export function outputText(message: AssistantMessage, options?: TextOptions): string {
33
+ return message.api === 'anthropic-messages'
34
+ ? anthropic.outputText(items(message))
35
+ : responses.outputText(items(message), options);
36
+ }
37
+
38
+ /** What the model refused to do, in order. Anthropic says so in the stop reason instead, so its messages have none. */
39
+ export function refusals(message: AssistantMessage): string[] {
40
+ return message.api === 'anthropic-messages'
41
+ ? anthropic.refusals()
42
+ : responses.refusals(items(message));
43
+ }
44
+
45
+ /** Reasoning summaries or thinking texts, in order. Encrypted reasoning stays in the message. */
46
+ export function reasoningSummary(message: AssistantMessage): string[] {
47
+ return message.api === 'anthropic-messages'
48
+ ? anthropic.reasoningSummary(items(message))
49
+ : responses.reasoningSummary(items(message));
50
+ }
51
+
52
+ /** Every call in the message that the caller answers, answered within it or not. */
53
+ export function toolCalls<P extends Provider>(message: AssistantMessage<P>): ToolCall<P>[] {
54
+ const any: AssistantMessage = message;
55
+ return typed<P>(
56
+ any.api === 'anthropic-messages'
57
+ ? anthropic.toolCalls(items(any))
58
+ : responses.toolCalls(items(any)),
59
+ );
60
+ }
61
+
62
+ /** Calls the caller still has to answer. An async call may stay pending while the conversation goes on. */
63
+ export function pendingToolCalls<P extends Provider>(message: AssistantMessage<P>): ToolCall<P>[] {
64
+ const any: AssistantMessage = message;
65
+ return typed<P>(
66
+ any.api === 'anthropic-messages'
67
+ ? anthropic.toolCalls(items(any))
68
+ : responses.pendingToolCalls(items(any)),
69
+ );
70
+ }
71
+
72
+ export type ToolResultOptions = {
73
+ /** Sent only where the provider has a field for it, so the content must say what failed. */
74
+ readonly isError?: boolean;
75
+ };
76
+
77
+ /**
78
+ * The message answering a `function` or `custom` call, with the fields the
79
+ * provider requires on its result, such as OpenAI's `caller`. A `native` call
80
+ * is answered with a `native` message instead.
81
+ */
82
+ export function toolResult(
83
+ call: ToolCall,
84
+ content: string | readonly ContentPart[],
85
+ { isError }: ToolResultOptions = {},
86
+ ): ToolResultMessage {
87
+ if (call.toolType === 'native') {
88
+ throw new TypeError(
89
+ `${call.itemType} is a provider-defined call: answer it with a native message`,
90
+ );
91
+ }
92
+ return {
93
+ role: 'tool_result',
94
+ toolCallId: call.toolCallId,
95
+ toolName: call.toolName,
96
+ toolType: call.toolType,
97
+ content: typeof content === 'string' ? [{ type: 'text', text: content }] : content,
98
+ ...(isError === undefined ? {} : { isError }),
99
+ ...(call.resultOptions === undefined ? {} : { providerOptions: call.resultOptions }),
100
+ };
101
+ }
102
+
103
+ /** The messages a provider receives: the application's own `custom` messages left out. */
104
+ export function requestMessages<P extends Provider>(
105
+ messages: readonly Message<P>[],
106
+ ): RequestMessage<P>[] {
107
+ return messages.filter((message): message is RequestMessage<P> => message.role !== 'custom');
108
+ }
package/src/request.ts ADDED
@@ -0,0 +1,28 @@
1
+ // What a handler asks the client for. It holds no credentials: the client
2
+ // reads them from the handler's secrets, so they never reach args or the log.
3
+
4
+ import type { TextPart } from './content.js';
5
+ import type { Message } from './messages.js';
6
+ import type { ModelId, Provider, ProviderOptions } from './providers.js';
7
+ import type { ToolDefinition } from './tools.js';
8
+
9
+ /**
10
+ * One completion from provider `P`. Distributes over providers, so the model
11
+ * IDs, options and messages sent back all belong to `provider`.
12
+ */
13
+ export type LlmRequest<P extends Provider = Provider> = P extends Provider
14
+ ? {
15
+ readonly provider: P;
16
+ readonly model: ModelId<P>;
17
+ /** The provider's top-level system instructions. */
18
+ readonly instructions?: string | readonly TextPart[];
19
+ /** The conversation. `custom` messages are left out when sending. */
20
+ readonly messages: readonly Message<P>[];
21
+ /** Tools the caller runs. Hosted tools go in `options.tools`. */
22
+ readonly tools?: readonly ToolDefinition[];
23
+ /** The provider's own request fields, passed through. */
24
+ readonly options?: ProviderOptions<P>;
25
+ /** The most the call may take, in milliseconds. */
26
+ readonly timeoutMs?: number;
27
+ }
28
+ : never;
@@ -0,0 +1,44 @@
1
+ // What a completion returns, the same for every provider apart from the
2
+ // assistant message and `native`, which are the provider's own.
3
+
4
+ import type { AssistantMessage } from './messages.js';
5
+ import type { ModelId, NativeResponse, Provider } from './providers.js';
6
+ import type { Usage } from './usage.js';
7
+
8
+ export type StopReason =
9
+ /** Generation ended normally, a stop sequence included. */
10
+ | 'stop'
11
+ /** Cut short by an output token or length limit. */
12
+ | 'length'
13
+ /** The model called tools for the caller to run. */
14
+ | 'tool_use'
15
+ /** The model declined. */
16
+ | 'refusal'
17
+ /** A provider's safety filter blocked or ended generation. */
18
+ | 'content_filter'
19
+ /** The provider paused a turn that can be continued, such as during a long server tool run. */
20
+ | 'pause_turn';
21
+
22
+ /** One completion from provider `P`. */
23
+ export type LlmResponse<P extends Provider = Provider> = P extends Provider
24
+ ? {
25
+ /** The provider's response ID, such as for `previous_response_id`. */
26
+ readonly id?: string;
27
+ readonly provider: P;
28
+ /** The model ID the request asked for. */
29
+ readonly model: ModelId<P>;
30
+ /** The model the provider says answered, when it differs. */
31
+ readonly responseModel?: string;
32
+ readonly message: AssistantMessage<P>;
33
+ readonly stopReason: StopReason;
34
+ /** The provider's own reason, as it is, when it says more than `stopReason`. */
35
+ readonly providerStopReason?: string;
36
+ readonly usage?: Usage;
37
+ /** The provider's response fields that say something new: not its output or echoes of the request. */
38
+ readonly native: NativeResponse<P>;
39
+ /** How long the provider took, in milliseconds. */
40
+ readonly durationMs: number;
41
+ /** When the response arrived, in milliseconds since the Unix epoch. */
42
+ readonly timestamp: number;
43
+ }
44
+ : never;
package/src/secrets.ts ADDED
@@ -0,0 +1,40 @@
1
+ // The secrets the client reads, by name. Runners and the CLI import these
2
+ // names to pass the matching environment variables to handlers, so no
3
+ // runner names a provider itself.
4
+ //
5
+ // The client calls a provider directly when its key is set, and otherwise
6
+ // the gateway when both gateway secrets are set. A secret that is not set
7
+ // rejects with SecretNotSet on every runner (RUNNERS.md §8), which is how the
8
+ // client tells which are set.
9
+
10
+ import type { Provider } from './providers.js';
11
+
12
+ /** The secret holding each provider's API key. */
13
+ export const PROVIDER_SECRETS = {
14
+ anthropic: 'ANTHROPIC_API_KEY',
15
+ openai: 'OPENAI_API_KEY',
16
+ fireworks: 'FIREWORKS_API_KEY',
17
+ } as const satisfies { readonly [P in Provider]: string };
18
+
19
+ /** The gateway's address and token, which acentric-cloud sets for agents that use its gateway. */
20
+ export const GATEWAY_SECRETS = {
21
+ url: 'ACENTRIC_LLM_URL',
22
+ token: 'ACENTRIC_LLM_TOKEN',
23
+ } as const;
24
+
25
+ /**
26
+ * The secrets the client reads, taken from an environment where they are set:
27
+ * what a runner on your own machine passes to handlers, such as
28
+ * `createMemoryRunner({ secrets: llmSecrets(process.env) })`.
29
+ */
30
+ export function llmSecrets(env: { readonly [name: string]: string | undefined }): {
31
+ [name: string]: string;
32
+ } {
33
+ const names = [...Object.values(PROVIDER_SECRETS), ...Object.values(GATEWAY_SECRETS)];
34
+ const found: { [name: string]: string } = {};
35
+ for (const name of names) {
36
+ const value = env[name];
37
+ if (value !== undefined && value !== '') found[name] = value;
38
+ }
39
+ return found;
40
+ }
@@ -0,0 +1,33 @@
1
+ // A tool call as the readers report it: a view of an item in an assistant
2
+ // message, which keeps the call itself.
3
+
4
+ import type { ProviderFields } from './fields.js';
5
+ import type { NativeOutputItem, Provider } from './providers.js';
6
+
7
+ export type ToolCall<P extends Provider = Provider> = P extends Provider
8
+ ? {
9
+ /** The ID a result answers. */
10
+ readonly toolCallId: string;
11
+ /** The tool's name, or the item type for a provider-defined tool without one. */
12
+ readonly toolName: string;
13
+ /**
14
+ * `function` and `custom` calls are answered with a `tool_result` message,
15
+ * `native` ones (a provider-defined tool such as computer use, or an
16
+ * approval request) with a `native` message.
17
+ */
18
+ readonly toolType: 'function' | 'custom' | 'native';
19
+ /** A function call's arguments as the model wrote them: a JSON string, not parsed. */
20
+ readonly arguments?: string;
21
+ /** A custom tool call's text. */
22
+ readonly input?: string;
23
+ readonly namespace?: string;
24
+ /** The call may stay unanswered while the conversation goes on. */
25
+ readonly async?: boolean;
26
+ /** Fields the provider requires on this call's result; `toolResult` copies them. */
27
+ readonly resultOptions?: ProviderFields;
28
+ /** The provider's item type, such as `function_call` or `tool_use`. */
29
+ readonly itemType: string;
30
+ /** The call as the provider wrote it. */
31
+ readonly item: NativeOutputItem<P>;
32
+ }
33
+ : never;
package/src/tools.ts ADDED
@@ -0,0 +1,38 @@
1
+ // Tools the caller runs. A provider's hosted tools (web search, code
2
+ // execution, ...) are its own definitions, passed in the request's options.
3
+
4
+ import type { JsonObject } from '@acentric/types';
5
+
6
+ import type { ProviderFields } from './fields.js';
7
+
8
+ /** A JSON Schema object. */
9
+ export type JsonSchema = JsonObject;
10
+
11
+ type ToolBase = {
12
+ readonly name: string;
13
+ readonly description: string;
14
+ /** Merged into the provider's tool definition, such as Anthropic's `cache_control`. */
15
+ readonly providerOptions?: ProviderFields;
16
+ };
17
+
18
+ /** A tool called with JSON arguments matching `parameters`. */
19
+ export type FunctionTool = ToolBase & {
20
+ readonly type: 'function';
21
+ readonly parameters: JsonSchema;
22
+ /** Asks the provider to hold the arguments to the schema, where it can. */
23
+ readonly strict?: boolean;
24
+ };
25
+
26
+ /** A tool called with free-form text, optionally held to a grammar. Not every provider has them. */
27
+ export type CustomTool = ToolBase & {
28
+ readonly type: 'custom';
29
+ readonly format?: CustomToolFormat;
30
+ };
31
+
32
+ export type CustomToolFormat =
33
+ | { readonly type: 'text' }
34
+ | { readonly type: 'grammar'; readonly syntax: 'lark' | 'regex'; readonly definition: string };
35
+
36
+ export type ToolDefinition = FunctionTool | CustomTool;
37
+
38
+ export type ToolType = ToolDefinition['type'];
package/src/usage.ts ADDED
@@ -0,0 +1,33 @@
1
+ // Tokens and cost, the same for every provider.
2
+
3
+ /** Token counts. A count the provider does not report is left out; zero is a known zero. */
4
+ export type Usage = {
5
+ /** Every prompt token, cached or not. */
6
+ readonly totalInput?: number;
7
+ /** Prompt tokens neither read from nor written to a cache. */
8
+ readonly input?: number;
9
+ /** Output tokens, reasoning included where the provider counts it as output. */
10
+ readonly output?: number;
11
+ /** Reasoning tokens: part of `output` unless the provider bills them apart. */
12
+ readonly reasoning?: number;
13
+ readonly cacheRead?: number;
14
+ /** Every token written to a cache, whatever its time to live. */
15
+ readonly cacheWrite?: number;
16
+ /**
17
+ * What it cost: what the provider says it charged where it says so,
18
+ * otherwise estimated from the model's prices when they and every count
19
+ * needed are known.
20
+ */
21
+ readonly cost?: UsageCost;
22
+ };
23
+
24
+ /** US dollars. A part that is not known is left out. */
25
+ export type UsageCost = {
26
+ /** `reported`: what the provider says it charged. `estimated`: tokens at known prices. */
27
+ readonly source: 'reported' | 'estimated';
28
+ readonly input?: number;
29
+ readonly output?: number;
30
+ readonly cacheRead?: number;
31
+ readonly cacheWrite?: number;
32
+ readonly total: number;
33
+ };
package/src/version.ts ADDED
@@ -0,0 +1,6 @@
1
+ // This package's version. The client sends it to the gateway, so a gateway
2
+ // running an older copy can refuse a newer client's request plainly instead
3
+ // of failing on a shape it does not know. A test keeps it equal to
4
+ // package.json's version.
5
+
6
+ export const LLM_VERSION = '0.1.0';