@agentic-kit/protocol 1.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.
- package/LICENSE +22 -0
- package/README.md +55 -0
- package/base-url.d.ts +1 -0
- package/base-url.js +10 -0
- package/esm/base-url.js +7 -0
- package/esm/event-stream.js +77 -0
- package/esm/index.js +5 -0
- package/esm/json.js +71 -0
- package/esm/messages.js +147 -0
- package/esm/package.json +1 -0
- package/esm/types.js +1 -0
- package/event-stream.d.ts +19 -0
- package/event-stream.js +83 -0
- package/index.d.ts +5 -0
- package/index.js +21 -0
- package/json.d.ts +11 -0
- package/json.js +76 -0
- package/messages.d.ts +33 -0
- package/messages.js +163 -0
- package/package.json +41 -0
- package/types.d.ts +232 -0
- package/types.js +2 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Dan Lynch <pyramation@gmail.com>
|
|
4
|
+
Copyright (c) 2025 Constructive <developers@constructive.io>
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# @agentic-kit/protocol
|
|
2
|
+
|
|
3
|
+
The shared protocol kernel for [agentic-kit](https://github.com/constructive-io/agentic-kit).
|
|
4
|
+
|
|
5
|
+
This package holds the provider-agnostic contracts and helpers that every adapter
|
|
6
|
+
and the top-level `agentic-kit` package build on:
|
|
7
|
+
|
|
8
|
+
- **Types** — `Context`, `Message`, `AssistantMessage`, `ModelDescriptor`, `Usage`,
|
|
9
|
+
content blocks, and the provider/stream interfaces.
|
|
10
|
+
- **Event stream** — `EventStream` / `createAssistantMessageEventStream` for
|
|
11
|
+
incremental assistant responses.
|
|
12
|
+
- **Message helpers** — `createEmptyUsage`, `calculateUsageCost`, `getMessageText`,
|
|
13
|
+
`normalizeContext`, `createAssistantMessage`.
|
|
14
|
+
- **JSON helpers** — `clone`, `parsePartialJson`, `completePartialJson` for snapshotting
|
|
15
|
+
and recovering streamed tool-call arguments.
|
|
16
|
+
- **Base URL** — `normalizeBaseUrl`.
|
|
17
|
+
|
|
18
|
+
It has no runtime dependencies, so a provider adapter (`@agentic-kit/openai`,
|
|
19
|
+
`@agentic-kit/anthropic`, `@agentic-kit/ollama`) can depend on it standalone without
|
|
20
|
+
pulling in the rest of the framework.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Related Constructive Tooling
|
|
25
|
+
|
|
26
|
+
### 🤖 AI & Agent Development
|
|
27
|
+
|
|
28
|
+
* [agentic-kit](https://github.com/constructive-io/agentic-kit): **🧰 Provider-agnostic LLM adapter** with streaming and multi-turn support. Swap between Anthropic, OpenAI, Ollama, and any OpenAI-compatible endpoint.
|
|
29
|
+
|
|
30
|
+
### 📦 Package Management
|
|
31
|
+
|
|
32
|
+
* [pgpm](https://github.com/constructive-io/constructive/tree/main/pgpm/pgpm): **🖥️ PostgreSQL Package Manager** for modular Postgres development. Works with database workspaces, scaffolding, migrations, seeding, and installing database packages.
|
|
33
|
+
|
|
34
|
+
### 🧪 Testing
|
|
35
|
+
|
|
36
|
+
* [pgsql-test](https://github.com/constructive-io/constructive/tree/main/postgres/pgsql-test): **📊 Isolated testing environments** with per-test transaction rollbacks—ideal for integration tests, complex migrations, and RLS simulation.
|
|
37
|
+
* [pgsql-seed](https://github.com/constructive-io/constructive/tree/main/postgres/pgsql-seed): **🌱 PostgreSQL seeding utilities** for CSV, JSON, SQL data loading, and pgpm deployment.
|
|
38
|
+
* [supabase-test](https://github.com/constructive-io/constructive/tree/main/postgres/supabase-test): **🧪 Supabase-native test harness** preconfigured for the local Supabase stack—per-test rollbacks, JWT/role context helpers, and CI/GitHub Actions ready.
|
|
39
|
+
|
|
40
|
+
### 🧠 Parsing & AST
|
|
41
|
+
|
|
42
|
+
* [pgsql-parser](https://www.npmjs.com/package/pgsql-parser): **🔄 SQL conversion engine** that interprets and converts PostgreSQL syntax.
|
|
43
|
+
* [libpg-query-node](https://www.npmjs.com/package/libpg-query): **🌉 Node.js bindings** for `libpg_query`, converting SQL into parse trees.
|
|
44
|
+
* [@pgsql/types](https://www.npmjs.com/package/@pgsql/types): **📝 Type definitions** for PostgreSQL AST nodes in TypeScript.
|
|
45
|
+
* [@pgsql/utils](https://www.npmjs.com/package/@pgsql/utils): **🛠️ AST utilities** for constructing and transforming PostgreSQL syntax trees.
|
|
46
|
+
|
|
47
|
+
## Credits
|
|
48
|
+
|
|
49
|
+
**🛠 Built by the [Constructive](https://constructive.io) team — creators of modular Postgres tooling for secure, composable backends. If you like our work, contribute on [GitHub](https://github.com/constructive-io).**
|
|
50
|
+
|
|
51
|
+
## Disclaimer
|
|
52
|
+
|
|
53
|
+
AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.
|
|
54
|
+
|
|
55
|
+
No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.
|
package/base-url.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function normalizeBaseUrl(baseUrl: string): string;
|
package/base-url.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeBaseUrl = normalizeBaseUrl;
|
|
4
|
+
function normalizeBaseUrl(baseUrl) {
|
|
5
|
+
const trimmed = baseUrl.replace(/\/+$/, '');
|
|
6
|
+
if (/\/v\d+$/.test(trimmed)) {
|
|
7
|
+
return trimmed;
|
|
8
|
+
}
|
|
9
|
+
return `${trimmed}/v1`;
|
|
10
|
+
}
|
package/esm/base-url.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export class EventStream {
|
|
2
|
+
isTerminal;
|
|
3
|
+
extractResult;
|
|
4
|
+
queue = [];
|
|
5
|
+
waiting = [];
|
|
6
|
+
done = false;
|
|
7
|
+
finalResultPromise;
|
|
8
|
+
resolveFinalResult;
|
|
9
|
+
constructor(isTerminal, extractResult) {
|
|
10
|
+
this.isTerminal = isTerminal;
|
|
11
|
+
this.extractResult = extractResult;
|
|
12
|
+
this.finalResultPromise = new Promise((resolve) => {
|
|
13
|
+
this.resolveFinalResult = resolve;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
push(event) {
|
|
17
|
+
if (this.done) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (this.isTerminal(event)) {
|
|
21
|
+
this.done = true;
|
|
22
|
+
this.resolveFinalResult(this.extractResult(event));
|
|
23
|
+
}
|
|
24
|
+
const waiter = this.waiting.shift();
|
|
25
|
+
if (waiter) {
|
|
26
|
+
waiter({ value: event, done: false });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
this.queue.push(event);
|
|
30
|
+
}
|
|
31
|
+
end(result) {
|
|
32
|
+
this.done = true;
|
|
33
|
+
if (result !== undefined) {
|
|
34
|
+
this.resolveFinalResult(result);
|
|
35
|
+
}
|
|
36
|
+
while (this.waiting.length > 0) {
|
|
37
|
+
this.waiting.shift()({ value: undefined, done: true });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async *[Symbol.asyncIterator]() {
|
|
41
|
+
while (true) {
|
|
42
|
+
if (this.queue.length > 0) {
|
|
43
|
+
yield this.queue.shift();
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (this.done) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const next = await new Promise((resolve) => {
|
|
50
|
+
this.waiting.push(resolve);
|
|
51
|
+
});
|
|
52
|
+
if (next.done) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
yield next.value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
result() {
|
|
59
|
+
return this.finalResultPromise;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export class DefaultAssistantMessageEventStream extends EventStream {
|
|
63
|
+
constructor() {
|
|
64
|
+
super((event) => event.type === 'done' || event.type === 'error', (event) => {
|
|
65
|
+
if (event.type === 'done') {
|
|
66
|
+
return event.message;
|
|
67
|
+
}
|
|
68
|
+
if (event.type === 'error') {
|
|
69
|
+
return event.error;
|
|
70
|
+
}
|
|
71
|
+
throw new Error('Unexpected terminal event');
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export function createAssistantMessageEventStream() {
|
|
76
|
+
return new DefaultAssistantMessageEventStream();
|
|
77
|
+
}
|
package/esm/index.js
ADDED
package/esm/json.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** Deep structural clone via JSON round-trip. Used to snapshot streamed messages. */
|
|
2
|
+
export function clone(value) {
|
|
3
|
+
return JSON.parse(JSON.stringify(value));
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Parse JSON that may be truncated mid-stream. Returns `{}` for empty/garbage
|
|
7
|
+
* input. Attempts a strict parse first, then closes any open strings/brackets
|
|
8
|
+
* and retries — so partial tool-call arguments deserialize as they arrive.
|
|
9
|
+
*/
|
|
10
|
+
export function parsePartialJson(raw) {
|
|
11
|
+
const trimmed = raw.trim();
|
|
12
|
+
if (!trimmed) {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(trimmed);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// continue
|
|
20
|
+
}
|
|
21
|
+
const completed = completePartialJson(trimmed);
|
|
22
|
+
if (!completed) {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(completed);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Close any open strings, objects, and arrays so a truncated fragment parses. */
|
|
33
|
+
export function completePartialJson(input) {
|
|
34
|
+
let output = input;
|
|
35
|
+
let inString = false;
|
|
36
|
+
let escaping = false;
|
|
37
|
+
const stack = [];
|
|
38
|
+
for (const char of input) {
|
|
39
|
+
if (escaping) {
|
|
40
|
+
escaping = false;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (char === '\\') {
|
|
44
|
+
escaping = true;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (char === '"') {
|
|
48
|
+
inString = !inString;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (inString) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (char === '{') {
|
|
55
|
+
stack.push('}');
|
|
56
|
+
}
|
|
57
|
+
else if (char === '[') {
|
|
58
|
+
stack.push(']');
|
|
59
|
+
}
|
|
60
|
+
else if (char === '}' || char === ']') {
|
|
61
|
+
stack.pop();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (inString) {
|
|
65
|
+
output += '"';
|
|
66
|
+
}
|
|
67
|
+
while (stack.length > 0) {
|
|
68
|
+
output += stack.pop();
|
|
69
|
+
}
|
|
70
|
+
return output;
|
|
71
|
+
}
|
package/esm/messages.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
export function createEmptyUsage() {
|
|
2
|
+
return {
|
|
3
|
+
input: 0,
|
|
4
|
+
output: 0,
|
|
5
|
+
reasoning: 0,
|
|
6
|
+
cacheRead: 0,
|
|
7
|
+
cacheWrite: 0,
|
|
8
|
+
totalTokens: 0,
|
|
9
|
+
cost: {
|
|
10
|
+
input: 0,
|
|
11
|
+
output: 0,
|
|
12
|
+
cacheRead: 0,
|
|
13
|
+
cacheWrite: 0,
|
|
14
|
+
total: 0,
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function calculateUsageCost(model, usage) {
|
|
19
|
+
const schedule = model.cost;
|
|
20
|
+
usage.cost.input = ((schedule?.input ?? 0) / 1_000_000) * usage.input;
|
|
21
|
+
usage.cost.output = ((schedule?.output ?? 0) / 1_000_000) * usage.output;
|
|
22
|
+
usage.cost.cacheRead = ((schedule?.cacheRead ?? 0) / 1_000_000) * usage.cacheRead;
|
|
23
|
+
usage.cost.cacheWrite = ((schedule?.cacheWrite ?? 0) / 1_000_000) * usage.cacheWrite;
|
|
24
|
+
usage.cost.total =
|
|
25
|
+
usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
|
|
26
|
+
}
|
|
27
|
+
export function snapshotUsage(usage) {
|
|
28
|
+
return { ...usage, cost: { ...usage.cost } };
|
|
29
|
+
}
|
|
30
|
+
export function addUsage(target, delta) {
|
|
31
|
+
target.input += delta.input;
|
|
32
|
+
target.output += delta.output;
|
|
33
|
+
target.reasoning += delta.reasoning;
|
|
34
|
+
target.cacheRead += delta.cacheRead;
|
|
35
|
+
target.cacheWrite += delta.cacheWrite;
|
|
36
|
+
target.totalTokens += delta.totalTokens;
|
|
37
|
+
target.cost.input += delta.cost.input;
|
|
38
|
+
target.cost.output += delta.cost.output;
|
|
39
|
+
target.cost.cacheRead += delta.cost.cacheRead;
|
|
40
|
+
target.cost.cacheWrite += delta.cost.cacheWrite;
|
|
41
|
+
target.cost.total += delta.cost.total;
|
|
42
|
+
return target;
|
|
43
|
+
}
|
|
44
|
+
export function createAssistantMessage(model) {
|
|
45
|
+
return {
|
|
46
|
+
role: 'assistant',
|
|
47
|
+
api: model.api,
|
|
48
|
+
provider: model.provider,
|
|
49
|
+
model: model.id,
|
|
50
|
+
content: [],
|
|
51
|
+
usage: createEmptyUsage(),
|
|
52
|
+
stopReason: 'stop',
|
|
53
|
+
timestamp: Date.now(),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function createTextContent(text = '') {
|
|
57
|
+
return { type: 'text', text };
|
|
58
|
+
}
|
|
59
|
+
export function createImageContent(data, mimeType) {
|
|
60
|
+
return { type: 'image', data, mimeType };
|
|
61
|
+
}
|
|
62
|
+
export function createToolCall(id, name) {
|
|
63
|
+
return { type: 'toolCall', id, name, arguments: {}, rawArguments: '' };
|
|
64
|
+
}
|
|
65
|
+
export function createUserMessage(content) {
|
|
66
|
+
return {
|
|
67
|
+
role: 'user',
|
|
68
|
+
content,
|
|
69
|
+
timestamp: Date.now(),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export function createToolResultMessage(toolCallId, toolName, content, isError = false) {
|
|
73
|
+
return {
|
|
74
|
+
role: 'toolResult',
|
|
75
|
+
toolCallId,
|
|
76
|
+
toolName,
|
|
77
|
+
content,
|
|
78
|
+
isError,
|
|
79
|
+
timestamp: Date.now(),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export function getMessageText(message) {
|
|
83
|
+
return message.content
|
|
84
|
+
.filter((block) => block.type === 'text')
|
|
85
|
+
.map((block) => block.text)
|
|
86
|
+
.join('');
|
|
87
|
+
}
|
|
88
|
+
const DEFAULT_DEFERRAL_TEXT = 'User did not respond. Continue.';
|
|
89
|
+
/**
|
|
90
|
+
* Synthesize toolResult messages for every toolCall that lacks both a decision
|
|
91
|
+
* and a paired toolResult. Returns input unchanged (referentially equal) when
|
|
92
|
+
* nothing needs synthesizing.
|
|
93
|
+
*
|
|
94
|
+
* Use case: user types a message instead of clicking approve/deny on a paused
|
|
95
|
+
* tool. The agent can't resume on text alone — every dangling toolCall needs
|
|
96
|
+
* a result before the next request. Compose with sendMessages to forward the
|
|
97
|
+
* conversation cleanly:
|
|
98
|
+
*
|
|
99
|
+
* await sendMessages([...injectDeferralResults(messages), createUserMessage(text)]);
|
|
100
|
+
*/
|
|
101
|
+
export function injectDeferralResults(messages, optionsOrText = {}) {
|
|
102
|
+
const opts = typeof optionsOrText === 'string' ? { deferralText: optionsOrText } : optionsOrText;
|
|
103
|
+
const deferralText = opts.deferralText ?? DEFAULT_DEFERRAL_TEXT;
|
|
104
|
+
const completed = new Set();
|
|
105
|
+
for (const m of messages) {
|
|
106
|
+
if (m.role === 'toolResult')
|
|
107
|
+
completed.add(m.toolCallId);
|
|
108
|
+
}
|
|
109
|
+
const synthetic = [];
|
|
110
|
+
for (const m of messages) {
|
|
111
|
+
if (m.role !== 'assistant')
|
|
112
|
+
continue;
|
|
113
|
+
for (const block of m.content) {
|
|
114
|
+
if (block.type !== 'toolCall')
|
|
115
|
+
continue;
|
|
116
|
+
if (completed.has(block.id))
|
|
117
|
+
continue;
|
|
118
|
+
if ('decision' in block && block.decision !== undefined)
|
|
119
|
+
continue;
|
|
120
|
+
const result = createToolResultMessage(block.id, block.name, [
|
|
121
|
+
{ type: 'text', text: deferralText },
|
|
122
|
+
]);
|
|
123
|
+
if (opts.details !== undefined)
|
|
124
|
+
result.details = opts.details;
|
|
125
|
+
synthetic.push(result);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (synthetic.length === 0)
|
|
129
|
+
return messages;
|
|
130
|
+
return [...messages, ...synthetic];
|
|
131
|
+
}
|
|
132
|
+
export function cloneMessage(message) {
|
|
133
|
+
return JSON.parse(JSON.stringify(message));
|
|
134
|
+
}
|
|
135
|
+
export function normalizeContext(context) {
|
|
136
|
+
return {
|
|
137
|
+
systemPrompt: context.systemPrompt,
|
|
138
|
+
tools: context.tools ?? [],
|
|
139
|
+
messages: context.messages.map((message) => ensureTimestamp(cloneMessage(message))),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function ensureTimestamp(message) {
|
|
143
|
+
if (typeof message.timestamp !== 'number') {
|
|
144
|
+
message.timestamp = Date.now();
|
|
145
|
+
}
|
|
146
|
+
return message;
|
|
147
|
+
}
|
package/esm/package.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
package/esm/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AssistantMessage, AssistantMessageEvent, AssistantMessageEventStream } from './types.js';
|
|
2
|
+
export declare class EventStream<TEvent, TResult = TEvent> implements AsyncIterable<TEvent> {
|
|
3
|
+
private readonly isTerminal;
|
|
4
|
+
private readonly extractResult;
|
|
5
|
+
private readonly queue;
|
|
6
|
+
private readonly waiting;
|
|
7
|
+
private done;
|
|
8
|
+
private readonly finalResultPromise;
|
|
9
|
+
private resolveFinalResult;
|
|
10
|
+
constructor(isTerminal: (event: TEvent) => boolean, extractResult: (event: TEvent) => TResult);
|
|
11
|
+
push(event: TEvent): void;
|
|
12
|
+
end(result?: TResult): void;
|
|
13
|
+
[Symbol.asyncIterator](): AsyncIterator<TEvent>;
|
|
14
|
+
result(): Promise<TResult>;
|
|
15
|
+
}
|
|
16
|
+
export declare class DefaultAssistantMessageEventStream extends EventStream<AssistantMessageEvent, AssistantMessage> implements AssistantMessageEventStream {
|
|
17
|
+
constructor();
|
|
18
|
+
}
|
|
19
|
+
export declare function createAssistantMessageEventStream(): DefaultAssistantMessageEventStream;
|
package/event-stream.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DefaultAssistantMessageEventStream = exports.EventStream = void 0;
|
|
4
|
+
exports.createAssistantMessageEventStream = createAssistantMessageEventStream;
|
|
5
|
+
class EventStream {
|
|
6
|
+
isTerminal;
|
|
7
|
+
extractResult;
|
|
8
|
+
queue = [];
|
|
9
|
+
waiting = [];
|
|
10
|
+
done = false;
|
|
11
|
+
finalResultPromise;
|
|
12
|
+
resolveFinalResult;
|
|
13
|
+
constructor(isTerminal, extractResult) {
|
|
14
|
+
this.isTerminal = isTerminal;
|
|
15
|
+
this.extractResult = extractResult;
|
|
16
|
+
this.finalResultPromise = new Promise((resolve) => {
|
|
17
|
+
this.resolveFinalResult = resolve;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
push(event) {
|
|
21
|
+
if (this.done) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (this.isTerminal(event)) {
|
|
25
|
+
this.done = true;
|
|
26
|
+
this.resolveFinalResult(this.extractResult(event));
|
|
27
|
+
}
|
|
28
|
+
const waiter = this.waiting.shift();
|
|
29
|
+
if (waiter) {
|
|
30
|
+
waiter({ value: event, done: false });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
this.queue.push(event);
|
|
34
|
+
}
|
|
35
|
+
end(result) {
|
|
36
|
+
this.done = true;
|
|
37
|
+
if (result !== undefined) {
|
|
38
|
+
this.resolveFinalResult(result);
|
|
39
|
+
}
|
|
40
|
+
while (this.waiting.length > 0) {
|
|
41
|
+
this.waiting.shift()({ value: undefined, done: true });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async *[Symbol.asyncIterator]() {
|
|
45
|
+
while (true) {
|
|
46
|
+
if (this.queue.length > 0) {
|
|
47
|
+
yield this.queue.shift();
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (this.done) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const next = await new Promise((resolve) => {
|
|
54
|
+
this.waiting.push(resolve);
|
|
55
|
+
});
|
|
56
|
+
if (next.done) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
yield next.value;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
result() {
|
|
63
|
+
return this.finalResultPromise;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.EventStream = EventStream;
|
|
67
|
+
class DefaultAssistantMessageEventStream extends EventStream {
|
|
68
|
+
constructor() {
|
|
69
|
+
super((event) => event.type === 'done' || event.type === 'error', (event) => {
|
|
70
|
+
if (event.type === 'done') {
|
|
71
|
+
return event.message;
|
|
72
|
+
}
|
|
73
|
+
if (event.type === 'error') {
|
|
74
|
+
return event.error;
|
|
75
|
+
}
|
|
76
|
+
throw new Error('Unexpected terminal event');
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.DefaultAssistantMessageEventStream = DefaultAssistantMessageEventStream;
|
|
81
|
+
function createAssistantMessageEventStream() {
|
|
82
|
+
return new DefaultAssistantMessageEventStream();
|
|
83
|
+
}
|
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./base-url.js"), exports);
|
|
18
|
+
__exportStar(require("./event-stream.js"), exports);
|
|
19
|
+
__exportStar(require("./json.js"), exports);
|
|
20
|
+
__exportStar(require("./messages.js"), exports);
|
|
21
|
+
__exportStar(require("./types.js"), exports);
|
package/json.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { JsonValue } from './types.js';
|
|
2
|
+
/** Deep structural clone via JSON round-trip. Used to snapshot streamed messages. */
|
|
3
|
+
export declare function clone<TValue>(value: TValue): TValue;
|
|
4
|
+
/**
|
|
5
|
+
* Parse JSON that may be truncated mid-stream. Returns `{}` for empty/garbage
|
|
6
|
+
* input. Attempts a strict parse first, then closes any open strings/brackets
|
|
7
|
+
* and retries — so partial tool-call arguments deserialize as they arrive.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parsePartialJson(raw: string): Record<string, JsonValue | undefined>;
|
|
10
|
+
/** Close any open strings, objects, and arrays so a truncated fragment parses. */
|
|
11
|
+
export declare function completePartialJson(input: string): string | undefined;
|
package/json.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.clone = clone;
|
|
4
|
+
exports.parsePartialJson = parsePartialJson;
|
|
5
|
+
exports.completePartialJson = completePartialJson;
|
|
6
|
+
/** Deep structural clone via JSON round-trip. Used to snapshot streamed messages. */
|
|
7
|
+
function clone(value) {
|
|
8
|
+
return JSON.parse(JSON.stringify(value));
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Parse JSON that may be truncated mid-stream. Returns `{}` for empty/garbage
|
|
12
|
+
* input. Attempts a strict parse first, then closes any open strings/brackets
|
|
13
|
+
* and retries — so partial tool-call arguments deserialize as they arrive.
|
|
14
|
+
*/
|
|
15
|
+
function parsePartialJson(raw) {
|
|
16
|
+
const trimmed = raw.trim();
|
|
17
|
+
if (!trimmed) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(trimmed);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// continue
|
|
25
|
+
}
|
|
26
|
+
const completed = completePartialJson(trimmed);
|
|
27
|
+
if (!completed) {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(completed);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Close any open strings, objects, and arrays so a truncated fragment parses. */
|
|
38
|
+
function completePartialJson(input) {
|
|
39
|
+
let output = input;
|
|
40
|
+
let inString = false;
|
|
41
|
+
let escaping = false;
|
|
42
|
+
const stack = [];
|
|
43
|
+
for (const char of input) {
|
|
44
|
+
if (escaping) {
|
|
45
|
+
escaping = false;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (char === '\\') {
|
|
49
|
+
escaping = true;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (char === '"') {
|
|
53
|
+
inString = !inString;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (inString) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (char === '{') {
|
|
60
|
+
stack.push('}');
|
|
61
|
+
}
|
|
62
|
+
else if (char === '[') {
|
|
63
|
+
stack.push(']');
|
|
64
|
+
}
|
|
65
|
+
else if (char === '}' || char === ']') {
|
|
66
|
+
stack.pop();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (inString) {
|
|
70
|
+
output += '"';
|
|
71
|
+
}
|
|
72
|
+
while (stack.length > 0) {
|
|
73
|
+
output += stack.pop();
|
|
74
|
+
}
|
|
75
|
+
return output;
|
|
76
|
+
}
|
package/messages.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AssistantMessage, Context, ImageContent, Message, ModelDescriptor, TextContent, ToolCallContent, ToolResultMessage, Usage, UserMessage } from './types.js';
|
|
2
|
+
export declare function createEmptyUsage(): Usage;
|
|
3
|
+
export declare function calculateUsageCost(model: ModelDescriptor, usage: Usage): void;
|
|
4
|
+
export declare function snapshotUsage(usage: Usage): Usage;
|
|
5
|
+
export declare function addUsage(target: Usage, delta: Usage): Usage;
|
|
6
|
+
export declare function createAssistantMessage(model: ModelDescriptor): AssistantMessage;
|
|
7
|
+
export declare function createTextContent(text?: string): TextContent;
|
|
8
|
+
export declare function createImageContent(data: string, mimeType: string): ImageContent;
|
|
9
|
+
export declare function createToolCall(id: string, name: string): ToolCallContent;
|
|
10
|
+
export declare function createUserMessage(content: UserMessage['content']): UserMessage;
|
|
11
|
+
export declare function createToolResultMessage(toolCallId: string, toolName: string, content: ToolResultMessage['content'], isError?: boolean): ToolResultMessage;
|
|
12
|
+
export declare function getMessageText(message: AssistantMessage): string;
|
|
13
|
+
export interface InjectDeferralResultsOptions {
|
|
14
|
+
/** Text body for synthesized results. Defaults to a generic continue prompt. */
|
|
15
|
+
deferralText?: string;
|
|
16
|
+
/** Stamped onto each synthesized result's `details` field — typically `{ deferred: true }` so renderers can hide a "Done" badge. */
|
|
17
|
+
details?: unknown;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Synthesize toolResult messages for every toolCall that lacks both a decision
|
|
21
|
+
* and a paired toolResult. Returns input unchanged (referentially equal) when
|
|
22
|
+
* nothing needs synthesizing.
|
|
23
|
+
*
|
|
24
|
+
* Use case: user types a message instead of clicking approve/deny on a paused
|
|
25
|
+
* tool. The agent can't resume on text alone — every dangling toolCall needs
|
|
26
|
+
* a result before the next request. Compose with sendMessages to forward the
|
|
27
|
+
* conversation cleanly:
|
|
28
|
+
*
|
|
29
|
+
* await sendMessages([...injectDeferralResults(messages), createUserMessage(text)]);
|
|
30
|
+
*/
|
|
31
|
+
export declare function injectDeferralResults(messages: Message[], optionsOrText?: InjectDeferralResultsOptions | string): Message[];
|
|
32
|
+
export declare function cloneMessage<TMessage extends Message>(message: TMessage): TMessage;
|
|
33
|
+
export declare function normalizeContext(context: Context): Context;
|
package/messages.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createEmptyUsage = createEmptyUsage;
|
|
4
|
+
exports.calculateUsageCost = calculateUsageCost;
|
|
5
|
+
exports.snapshotUsage = snapshotUsage;
|
|
6
|
+
exports.addUsage = addUsage;
|
|
7
|
+
exports.createAssistantMessage = createAssistantMessage;
|
|
8
|
+
exports.createTextContent = createTextContent;
|
|
9
|
+
exports.createImageContent = createImageContent;
|
|
10
|
+
exports.createToolCall = createToolCall;
|
|
11
|
+
exports.createUserMessage = createUserMessage;
|
|
12
|
+
exports.createToolResultMessage = createToolResultMessage;
|
|
13
|
+
exports.getMessageText = getMessageText;
|
|
14
|
+
exports.injectDeferralResults = injectDeferralResults;
|
|
15
|
+
exports.cloneMessage = cloneMessage;
|
|
16
|
+
exports.normalizeContext = normalizeContext;
|
|
17
|
+
function createEmptyUsage() {
|
|
18
|
+
return {
|
|
19
|
+
input: 0,
|
|
20
|
+
output: 0,
|
|
21
|
+
reasoning: 0,
|
|
22
|
+
cacheRead: 0,
|
|
23
|
+
cacheWrite: 0,
|
|
24
|
+
totalTokens: 0,
|
|
25
|
+
cost: {
|
|
26
|
+
input: 0,
|
|
27
|
+
output: 0,
|
|
28
|
+
cacheRead: 0,
|
|
29
|
+
cacheWrite: 0,
|
|
30
|
+
total: 0,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function calculateUsageCost(model, usage) {
|
|
35
|
+
const schedule = model.cost;
|
|
36
|
+
usage.cost.input = ((schedule?.input ?? 0) / 1_000_000) * usage.input;
|
|
37
|
+
usage.cost.output = ((schedule?.output ?? 0) / 1_000_000) * usage.output;
|
|
38
|
+
usage.cost.cacheRead = ((schedule?.cacheRead ?? 0) / 1_000_000) * usage.cacheRead;
|
|
39
|
+
usage.cost.cacheWrite = ((schedule?.cacheWrite ?? 0) / 1_000_000) * usage.cacheWrite;
|
|
40
|
+
usage.cost.total =
|
|
41
|
+
usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
|
|
42
|
+
}
|
|
43
|
+
function snapshotUsage(usage) {
|
|
44
|
+
return { ...usage, cost: { ...usage.cost } };
|
|
45
|
+
}
|
|
46
|
+
function addUsage(target, delta) {
|
|
47
|
+
target.input += delta.input;
|
|
48
|
+
target.output += delta.output;
|
|
49
|
+
target.reasoning += delta.reasoning;
|
|
50
|
+
target.cacheRead += delta.cacheRead;
|
|
51
|
+
target.cacheWrite += delta.cacheWrite;
|
|
52
|
+
target.totalTokens += delta.totalTokens;
|
|
53
|
+
target.cost.input += delta.cost.input;
|
|
54
|
+
target.cost.output += delta.cost.output;
|
|
55
|
+
target.cost.cacheRead += delta.cost.cacheRead;
|
|
56
|
+
target.cost.cacheWrite += delta.cost.cacheWrite;
|
|
57
|
+
target.cost.total += delta.cost.total;
|
|
58
|
+
return target;
|
|
59
|
+
}
|
|
60
|
+
function createAssistantMessage(model) {
|
|
61
|
+
return {
|
|
62
|
+
role: 'assistant',
|
|
63
|
+
api: model.api,
|
|
64
|
+
provider: model.provider,
|
|
65
|
+
model: model.id,
|
|
66
|
+
content: [],
|
|
67
|
+
usage: createEmptyUsage(),
|
|
68
|
+
stopReason: 'stop',
|
|
69
|
+
timestamp: Date.now(),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function createTextContent(text = '') {
|
|
73
|
+
return { type: 'text', text };
|
|
74
|
+
}
|
|
75
|
+
function createImageContent(data, mimeType) {
|
|
76
|
+
return { type: 'image', data, mimeType };
|
|
77
|
+
}
|
|
78
|
+
function createToolCall(id, name) {
|
|
79
|
+
return { type: 'toolCall', id, name, arguments: {}, rawArguments: '' };
|
|
80
|
+
}
|
|
81
|
+
function createUserMessage(content) {
|
|
82
|
+
return {
|
|
83
|
+
role: 'user',
|
|
84
|
+
content,
|
|
85
|
+
timestamp: Date.now(),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function createToolResultMessage(toolCallId, toolName, content, isError = false) {
|
|
89
|
+
return {
|
|
90
|
+
role: 'toolResult',
|
|
91
|
+
toolCallId,
|
|
92
|
+
toolName,
|
|
93
|
+
content,
|
|
94
|
+
isError,
|
|
95
|
+
timestamp: Date.now(),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function getMessageText(message) {
|
|
99
|
+
return message.content
|
|
100
|
+
.filter((block) => block.type === 'text')
|
|
101
|
+
.map((block) => block.text)
|
|
102
|
+
.join('');
|
|
103
|
+
}
|
|
104
|
+
const DEFAULT_DEFERRAL_TEXT = 'User did not respond. Continue.';
|
|
105
|
+
/**
|
|
106
|
+
* Synthesize toolResult messages for every toolCall that lacks both a decision
|
|
107
|
+
* and a paired toolResult. Returns input unchanged (referentially equal) when
|
|
108
|
+
* nothing needs synthesizing.
|
|
109
|
+
*
|
|
110
|
+
* Use case: user types a message instead of clicking approve/deny on a paused
|
|
111
|
+
* tool. The agent can't resume on text alone — every dangling toolCall needs
|
|
112
|
+
* a result before the next request. Compose with sendMessages to forward the
|
|
113
|
+
* conversation cleanly:
|
|
114
|
+
*
|
|
115
|
+
* await sendMessages([...injectDeferralResults(messages), createUserMessage(text)]);
|
|
116
|
+
*/
|
|
117
|
+
function injectDeferralResults(messages, optionsOrText = {}) {
|
|
118
|
+
const opts = typeof optionsOrText === 'string' ? { deferralText: optionsOrText } : optionsOrText;
|
|
119
|
+
const deferralText = opts.deferralText ?? DEFAULT_DEFERRAL_TEXT;
|
|
120
|
+
const completed = new Set();
|
|
121
|
+
for (const m of messages) {
|
|
122
|
+
if (m.role === 'toolResult')
|
|
123
|
+
completed.add(m.toolCallId);
|
|
124
|
+
}
|
|
125
|
+
const synthetic = [];
|
|
126
|
+
for (const m of messages) {
|
|
127
|
+
if (m.role !== 'assistant')
|
|
128
|
+
continue;
|
|
129
|
+
for (const block of m.content) {
|
|
130
|
+
if (block.type !== 'toolCall')
|
|
131
|
+
continue;
|
|
132
|
+
if (completed.has(block.id))
|
|
133
|
+
continue;
|
|
134
|
+
if ('decision' in block && block.decision !== undefined)
|
|
135
|
+
continue;
|
|
136
|
+
const result = createToolResultMessage(block.id, block.name, [
|
|
137
|
+
{ type: 'text', text: deferralText },
|
|
138
|
+
]);
|
|
139
|
+
if (opts.details !== undefined)
|
|
140
|
+
result.details = opts.details;
|
|
141
|
+
synthetic.push(result);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (synthetic.length === 0)
|
|
145
|
+
return messages;
|
|
146
|
+
return [...messages, ...synthetic];
|
|
147
|
+
}
|
|
148
|
+
function cloneMessage(message) {
|
|
149
|
+
return JSON.parse(JSON.stringify(message));
|
|
150
|
+
}
|
|
151
|
+
function normalizeContext(context) {
|
|
152
|
+
return {
|
|
153
|
+
systemPrompt: context.systemPrompt,
|
|
154
|
+
tools: context.tools ?? [],
|
|
155
|
+
messages: context.messages.map((message) => ensureTimestamp(cloneMessage(message))),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function ensureTimestamp(message) {
|
|
159
|
+
if (typeof message.timestamp !== 'number') {
|
|
160
|
+
message.timestamp = Date.now();
|
|
161
|
+
}
|
|
162
|
+
return message;
|
|
163
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentic-kit/protocol",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"author": "Dan Lynch <pyramation@gmail.com>",
|
|
5
|
+
"description": "Shared protocol kernel (types, event stream, message + JSON helpers) for agentic-kit",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"module": "esm/index.js",
|
|
8
|
+
"types": "index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./index.d.ts",
|
|
12
|
+
"import": "./esm/index.js",
|
|
13
|
+
"require": "./index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/constructive-io/agentic-kit",
|
|
17
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public",
|
|
20
|
+
"directory": "dist"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/constructive-io/agentic-kit"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/constructive-io/agentic-kit/issues"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"clean": "makage clean",
|
|
31
|
+
"prepack": "npm run build",
|
|
32
|
+
"build": "makage build",
|
|
33
|
+
"postbuild": "node ../../scripts/write-esm-package-json.js",
|
|
34
|
+
"build:dev": "makage build --dev",
|
|
35
|
+
"lint": "eslint . --fix",
|
|
36
|
+
"test": "jest",
|
|
37
|
+
"test:watch": "jest --watch"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [],
|
|
40
|
+
"gitHead": "fbf34d1146e8543664d904f75e5b3440f2b1d2a4"
|
|
41
|
+
}
|
package/types.d.ts
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
2
|
+
export type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
|
|
3
|
+
export interface JsonObject {
|
|
4
|
+
[key: string]: JsonValue | undefined;
|
|
5
|
+
}
|
|
6
|
+
export interface JsonSchema {
|
|
7
|
+
$id?: string;
|
|
8
|
+
$ref?: string;
|
|
9
|
+
additionalProperties?: boolean | JsonSchema;
|
|
10
|
+
allOf?: JsonSchema[];
|
|
11
|
+
anyOf?: JsonSchema[];
|
|
12
|
+
const?: JsonValue;
|
|
13
|
+
default?: JsonValue;
|
|
14
|
+
description?: string;
|
|
15
|
+
enum?: JsonValue[];
|
|
16
|
+
format?: string;
|
|
17
|
+
items?: JsonSchema | JsonSchema[];
|
|
18
|
+
maxItems?: number;
|
|
19
|
+
maxLength?: number;
|
|
20
|
+
maximum?: number;
|
|
21
|
+
minItems?: number;
|
|
22
|
+
minLength?: number;
|
|
23
|
+
minimum?: number;
|
|
24
|
+
oneOf?: JsonSchema[];
|
|
25
|
+
pattern?: string;
|
|
26
|
+
properties?: Record<string, JsonSchema>;
|
|
27
|
+
required?: string[];
|
|
28
|
+
title?: string;
|
|
29
|
+
type?: string | string[];
|
|
30
|
+
}
|
|
31
|
+
export type InputCapability = 'text' | 'image';
|
|
32
|
+
export interface CostSchedule {
|
|
33
|
+
input?: number;
|
|
34
|
+
output?: number;
|
|
35
|
+
cacheRead?: number;
|
|
36
|
+
cacheWrite?: number;
|
|
37
|
+
}
|
|
38
|
+
export interface OpenAICompatibleCompat {
|
|
39
|
+
maxTokensField?: 'max_completion_tokens' | 'max_tokens';
|
|
40
|
+
reasoningFormat?: 'none' | 'openai';
|
|
41
|
+
supportsReasoningEffort?: boolean;
|
|
42
|
+
supportsStrictToolSchema?: boolean;
|
|
43
|
+
supportsUsageInStreaming?: boolean;
|
|
44
|
+
toolCallIdFormat?: 'passthrough' | 'safe64' | 'mistral9';
|
|
45
|
+
requiresToolResultName?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface ModelDescriptor {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
api: string;
|
|
51
|
+
provider: string;
|
|
52
|
+
baseUrl: string;
|
|
53
|
+
input: InputCapability[];
|
|
54
|
+
reasoning: boolean;
|
|
55
|
+
tools?: boolean;
|
|
56
|
+
contextWindow?: number;
|
|
57
|
+
maxOutputTokens?: number;
|
|
58
|
+
cost?: CostSchedule;
|
|
59
|
+
headers?: Record<string, string>;
|
|
60
|
+
compat?: OpenAICompatibleCompat;
|
|
61
|
+
}
|
|
62
|
+
export interface TextContent {
|
|
63
|
+
type: 'text';
|
|
64
|
+
text: string;
|
|
65
|
+
}
|
|
66
|
+
export interface ImageContent {
|
|
67
|
+
type: 'image';
|
|
68
|
+
data: string;
|
|
69
|
+
mimeType: string;
|
|
70
|
+
}
|
|
71
|
+
export interface ThinkingContent {
|
|
72
|
+
type: 'thinking';
|
|
73
|
+
thinking: string;
|
|
74
|
+
signature?: string;
|
|
75
|
+
}
|
|
76
|
+
export interface ToolCallContent {
|
|
77
|
+
type: 'toolCall';
|
|
78
|
+
id: string;
|
|
79
|
+
name: string;
|
|
80
|
+
arguments: Record<string, JsonValue | undefined>;
|
|
81
|
+
rawArguments?: string;
|
|
82
|
+
decision?: unknown;
|
|
83
|
+
}
|
|
84
|
+
export interface Usage {
|
|
85
|
+
input: number;
|
|
86
|
+
output: number;
|
|
87
|
+
reasoning: number;
|
|
88
|
+
cacheRead: number;
|
|
89
|
+
cacheWrite: number;
|
|
90
|
+
totalTokens: number;
|
|
91
|
+
cost: {
|
|
92
|
+
input: number;
|
|
93
|
+
output: number;
|
|
94
|
+
cacheRead: number;
|
|
95
|
+
cacheWrite: number;
|
|
96
|
+
total: number;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export type StopReason = 'stop' | 'length' | 'toolUse' | 'error' | 'aborted';
|
|
100
|
+
export interface UserMessage {
|
|
101
|
+
role: 'user';
|
|
102
|
+
content: string | Array<TextContent | ImageContent>;
|
|
103
|
+
timestamp: number;
|
|
104
|
+
}
|
|
105
|
+
export interface AssistantMessage {
|
|
106
|
+
role: 'assistant';
|
|
107
|
+
content: Array<TextContent | ThinkingContent | ToolCallContent>;
|
|
108
|
+
api: string;
|
|
109
|
+
provider: string;
|
|
110
|
+
model: string;
|
|
111
|
+
usage: Usage;
|
|
112
|
+
stopReason: StopReason;
|
|
113
|
+
errorMessage?: string;
|
|
114
|
+
timestamp: number;
|
|
115
|
+
}
|
|
116
|
+
export interface ToolResultMessage<TDetails = unknown> {
|
|
117
|
+
role: 'toolResult';
|
|
118
|
+
toolCallId: string;
|
|
119
|
+
toolName: string;
|
|
120
|
+
content: Array<TextContent | ImageContent>;
|
|
121
|
+
isError: boolean;
|
|
122
|
+
details?: TDetails;
|
|
123
|
+
timestamp: number;
|
|
124
|
+
}
|
|
125
|
+
export type Message = UserMessage | AssistantMessage | ToolResultMessage;
|
|
126
|
+
export interface ToolDefinition<TSchema extends JsonSchema = JsonSchema> {
|
|
127
|
+
name: string;
|
|
128
|
+
description: string;
|
|
129
|
+
parameters: TSchema;
|
|
130
|
+
}
|
|
131
|
+
export interface Context {
|
|
132
|
+
systemPrompt?: string;
|
|
133
|
+
messages: Message[];
|
|
134
|
+
tools?: ToolDefinition[];
|
|
135
|
+
}
|
|
136
|
+
export type CacheRetention = 'none' | 'short' | 'long';
|
|
137
|
+
export interface StreamOptions {
|
|
138
|
+
apiKey?: string;
|
|
139
|
+
cacheRetention?: CacheRetention;
|
|
140
|
+
headers?: Record<string, string>;
|
|
141
|
+
maxRetryDelayMs?: number;
|
|
142
|
+
maxTokens?: number;
|
|
143
|
+
metadata?: Record<string, JsonValue | undefined>;
|
|
144
|
+
onPayload?: (payload: unknown) => void;
|
|
145
|
+
reasoning?: 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
|
|
146
|
+
sessionId?: string;
|
|
147
|
+
signal?: AbortSignal;
|
|
148
|
+
temperature?: number;
|
|
149
|
+
}
|
|
150
|
+
export type AssistantMessageEvent = {
|
|
151
|
+
type: 'start';
|
|
152
|
+
partial: AssistantMessage;
|
|
153
|
+
} | {
|
|
154
|
+
type: 'text_start';
|
|
155
|
+
contentIndex: number;
|
|
156
|
+
partial: AssistantMessage;
|
|
157
|
+
} | {
|
|
158
|
+
type: 'text_delta';
|
|
159
|
+
contentIndex: number;
|
|
160
|
+
delta: string;
|
|
161
|
+
partial: AssistantMessage;
|
|
162
|
+
} | {
|
|
163
|
+
type: 'text_end';
|
|
164
|
+
contentIndex: number;
|
|
165
|
+
content: string;
|
|
166
|
+
partial: AssistantMessage;
|
|
167
|
+
} | {
|
|
168
|
+
type: 'thinking_start';
|
|
169
|
+
contentIndex: number;
|
|
170
|
+
partial: AssistantMessage;
|
|
171
|
+
} | {
|
|
172
|
+
type: 'thinking_delta';
|
|
173
|
+
contentIndex: number;
|
|
174
|
+
delta: string;
|
|
175
|
+
partial: AssistantMessage;
|
|
176
|
+
} | {
|
|
177
|
+
type: 'thinking_end';
|
|
178
|
+
contentIndex: number;
|
|
179
|
+
content: string;
|
|
180
|
+
partial: AssistantMessage;
|
|
181
|
+
} | {
|
|
182
|
+
type: 'toolcall_start';
|
|
183
|
+
contentIndex: number;
|
|
184
|
+
partial: AssistantMessage;
|
|
185
|
+
} | {
|
|
186
|
+
type: 'toolcall_delta';
|
|
187
|
+
contentIndex: number;
|
|
188
|
+
delta: string;
|
|
189
|
+
partial: AssistantMessage;
|
|
190
|
+
} | {
|
|
191
|
+
type: 'toolcall_end';
|
|
192
|
+
contentIndex: number;
|
|
193
|
+
toolCall: ToolCallContent;
|
|
194
|
+
partial: AssistantMessage;
|
|
195
|
+
} | {
|
|
196
|
+
type: 'done';
|
|
197
|
+
reason: Extract<StopReason, 'stop' | 'length' | 'toolUse'>;
|
|
198
|
+
message: AssistantMessage;
|
|
199
|
+
} | {
|
|
200
|
+
type: 'error';
|
|
201
|
+
reason: Extract<StopReason, 'error' | 'aborted'>;
|
|
202
|
+
error: AssistantMessage;
|
|
203
|
+
};
|
|
204
|
+
export interface ProviderAdapter<TOptions extends StreamOptions = StreamOptions> {
|
|
205
|
+
api: string;
|
|
206
|
+
provider: string;
|
|
207
|
+
createModel?: (modelId: string, overrides?: Partial<ModelDescriptor>) => ModelDescriptor;
|
|
208
|
+
listModels?: (options?: Pick<StreamOptions, 'apiKey' | 'headers'>) => Promise<Array<ModelDescriptor | string>>;
|
|
209
|
+
stream: (model: ModelDescriptor, context: Context, options?: TOptions) => AssistantMessageEventStream;
|
|
210
|
+
}
|
|
211
|
+
export interface LegacyChatMessage {
|
|
212
|
+
role: 'system' | 'user' | 'assistant';
|
|
213
|
+
content: string;
|
|
214
|
+
}
|
|
215
|
+
export interface LegacyGenerateInput {
|
|
216
|
+
model: string;
|
|
217
|
+
prompt?: string;
|
|
218
|
+
messages?: LegacyChatMessage[];
|
|
219
|
+
system?: string;
|
|
220
|
+
stream?: boolean;
|
|
221
|
+
temperature?: number;
|
|
222
|
+
maxTokens?: number;
|
|
223
|
+
}
|
|
224
|
+
export interface LegacyStreamingOptions {
|
|
225
|
+
onChunk?: (chunk: string) => void;
|
|
226
|
+
onComplete?: () => void;
|
|
227
|
+
onError?: (error: Error) => void;
|
|
228
|
+
onStateChange?: (state: string) => void;
|
|
229
|
+
}
|
|
230
|
+
export interface AssistantMessageEventStream extends AsyncIterable<AssistantMessageEvent> {
|
|
231
|
+
result(): Promise<AssistantMessage>;
|
|
232
|
+
}
|
package/types.js
ADDED