@poncho-ai/client 0.2.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.
@@ -0,0 +1,14 @@
1
+
2
+ > @poncho-ai/client@0.2.0 build /Users/cesar/Dev/latitude/poncho-ai/packages/client
3
+ > tsup src/index.ts --format esm --dts
4
+
5
+ CLI Building entry: src/index.ts
6
+ CLI Using tsconfig: tsconfig.json
7
+ CLI tsup v8.5.1
8
+ CLI Target: es2022
9
+ ESM Build start
10
+ ESM dist/index.js 6.32 KB
11
+ ESM ⚡️ Build success in 19ms
12
+ DTS Build start
13
+ DTS ⚡️ Build success in 977ms
14
+ DTS dist/index.d.ts 1.83 KB
@@ -0,0 +1,14 @@
1
+
2
+ > @poncho-ai/client@0.1.0 test /Users/cesar/Dev/latitude/agentl/packages/client
3
+ > vitest
4
+
5
+
6
+  RUN  v1.6.1 /Users/cesar/Dev/latitude/agentl/packages/client
7
+
8
+ ✓ test/client.test.ts  (3 tests) 5ms
9
+
10
+  Test Files  1 passed (1)
11
+  Tests  3 passed (3)
12
+  Start at  12:05:59
13
+  Duration  340ms (transform 43ms, setup 0ms, collect 50ms, tests 5ms, environment 0ms, prepare 64ms)
14
+
package/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # @poncho-ai/client
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Initial release of Poncho - an open framework for building and deploying AI agents.
8
+ - `@poncho-ai/sdk`: Core types and utilities for building Poncho skills
9
+ - `@poncho-ai/harness`: Agent execution runtime with conversation loop, tool dispatch, and streaming
10
+ - `@poncho-ai/client`: TypeScript client for calling deployed Poncho agents
11
+ - `@poncho-ai/cli`: CLI for building and deploying AI agents
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies []:
16
+ - @poncho-ai/sdk@0.2.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Latitude
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,56 @@
1
+ import { RunResult, RunInput, AgentEvent } from '@poncho-ai/sdk';
2
+
3
+ interface AgentClientOptions {
4
+ url: string;
5
+ apiKey?: string;
6
+ fetchImpl?: typeof fetch;
7
+ }
8
+ interface SyncRunResponse {
9
+ runId: string;
10
+ status: RunResult["status"];
11
+ result: RunResult;
12
+ }
13
+ interface ContinueInput {
14
+ runId: string;
15
+ message: string;
16
+ parameters?: Record<string, unknown>;
17
+ }
18
+ interface ConversationSummary {
19
+ conversationId: string;
20
+ title: string;
21
+ runtimeRunId?: string;
22
+ ownerId: string;
23
+ tenantId: string | null;
24
+ createdAt: number;
25
+ updatedAt: number;
26
+ messageCount: number;
27
+ }
28
+ interface ConversationRecord extends Omit<ConversationSummary, "messageCount"> {
29
+ messages: Array<{
30
+ role: "user" | "assistant";
31
+ content: string;
32
+ }>;
33
+ }
34
+ declare class AgentClient {
35
+ private readonly baseUrl;
36
+ private readonly apiKey?;
37
+ private readonly fetchImpl;
38
+ constructor(options: AgentClientOptions);
39
+ private headers;
40
+ private parseSse;
41
+ listConversations(): Promise<ConversationSummary[]>;
42
+ createConversation(input?: {
43
+ title?: string;
44
+ }): Promise<ConversationRecord>;
45
+ getConversation(conversationId: string): Promise<ConversationRecord>;
46
+ deleteConversation(conversationId: string): Promise<void>;
47
+ sendMessage(conversationId: string, message: string, parameters?: Record<string, unknown>): Promise<SyncRunResponse>;
48
+ run(input: RunInput): Promise<SyncRunResponse>;
49
+ continue(input: ContinueInput): Promise<SyncRunResponse>;
50
+ conversation(initialRunId?: string): {
51
+ send: (message: string, parameters?: Record<string, unknown>) => Promise<SyncRunResponse>;
52
+ };
53
+ stream(input: RunInput): AsyncGenerator<AgentEvent>;
54
+ }
55
+
56
+ export { AgentClient, type AgentClientOptions, type ContinueInput, type ConversationRecord, type ConversationSummary, type SyncRunResponse };
package/dist/index.js ADDED
@@ -0,0 +1,203 @@
1
+ // src/index.ts
2
+ var trimTrailingSlash = (value) => value.replace(/\/+$/, "");
3
+ var AgentClient = class {
4
+ baseUrl;
5
+ apiKey;
6
+ fetchImpl;
7
+ constructor(options) {
8
+ this.baseUrl = trimTrailingSlash(options.url);
9
+ this.apiKey = options.apiKey;
10
+ this.fetchImpl = options.fetchImpl ?? fetch;
11
+ }
12
+ headers() {
13
+ const headers = {
14
+ "Content-Type": "application/json"
15
+ };
16
+ if (this.apiKey) {
17
+ headers.Authorization = `Bearer ${this.apiKey}`;
18
+ }
19
+ return headers;
20
+ }
21
+ async parseSse(response) {
22
+ if (!response.body) {
23
+ throw new Error("Missing response body");
24
+ }
25
+ const reader = response.body.getReader();
26
+ const decoder = new TextDecoder();
27
+ let buffer = "";
28
+ const events = [];
29
+ while (true) {
30
+ const { done, value } = await reader.read();
31
+ if (done) {
32
+ break;
33
+ }
34
+ buffer += decoder.decode(value, { stream: true });
35
+ const frames = buffer.split("\n\n");
36
+ buffer = frames.pop() ?? "";
37
+ for (const frame of frames) {
38
+ const lines = frame.split("\n").map((line) => line.trim()).filter(Boolean);
39
+ const dataLine = lines.find((line) => line.startsWith("data:"));
40
+ if (!dataLine) {
41
+ continue;
42
+ }
43
+ const payload = JSON.parse(dataLine.slice("data:".length).trim());
44
+ events.push(payload);
45
+ }
46
+ }
47
+ return events;
48
+ }
49
+ async listConversations() {
50
+ const response = await this.fetchImpl(`${this.baseUrl}/api/conversations`, {
51
+ method: "GET",
52
+ headers: this.headers()
53
+ });
54
+ if (!response.ok) {
55
+ throw new Error(`List conversations failed: HTTP ${response.status}`);
56
+ }
57
+ const payload = await response.json();
58
+ return payload.conversations;
59
+ }
60
+ async createConversation(input) {
61
+ const response = await this.fetchImpl(`${this.baseUrl}/api/conversations`, {
62
+ method: "POST",
63
+ headers: this.headers(),
64
+ body: JSON.stringify(input ?? {})
65
+ });
66
+ if (!response.ok) {
67
+ throw new Error(`Create conversation failed: HTTP ${response.status}`);
68
+ }
69
+ const payload = await response.json();
70
+ return payload.conversation;
71
+ }
72
+ async getConversation(conversationId) {
73
+ const response = await this.fetchImpl(
74
+ `${this.baseUrl}/api/conversations/${encodeURIComponent(conversationId)}`,
75
+ {
76
+ method: "GET",
77
+ headers: this.headers()
78
+ }
79
+ );
80
+ if (!response.ok) {
81
+ throw new Error(`Get conversation failed: HTTP ${response.status}`);
82
+ }
83
+ const payload = await response.json();
84
+ return payload.conversation;
85
+ }
86
+ async deleteConversation(conversationId) {
87
+ const response = await this.fetchImpl(
88
+ `${this.baseUrl}/api/conversations/${encodeURIComponent(conversationId)}`,
89
+ {
90
+ method: "DELETE",
91
+ headers: this.headers()
92
+ }
93
+ );
94
+ if (!response.ok) {
95
+ throw new Error(`Delete conversation failed: HTTP ${response.status}`);
96
+ }
97
+ }
98
+ async sendMessage(conversationId, message, parameters) {
99
+ const response = await this.fetchImpl(
100
+ `${this.baseUrl}/api/conversations/${encodeURIComponent(conversationId)}/messages`,
101
+ {
102
+ method: "POST",
103
+ headers: this.headers(),
104
+ body: JSON.stringify({ message, parameters })
105
+ }
106
+ );
107
+ if (!response.ok) {
108
+ throw new Error(`Send message failed: HTTP ${response.status}`);
109
+ }
110
+ const events = await this.parseSse(response);
111
+ const runStarted = events.find(
112
+ (event) => event.type === "run:started"
113
+ );
114
+ const completed = events.find(
115
+ (event) => event.type === "run:completed"
116
+ );
117
+ if (!completed) {
118
+ throw new Error("Send message failed: missing run:completed event");
119
+ }
120
+ return {
121
+ runId: runStarted?.runId ?? completed.runId,
122
+ status: completed.result.status,
123
+ result: completed.result
124
+ };
125
+ }
126
+ async run(input) {
127
+ if ((input.messages?.length ?? 0) > 0) {
128
+ throw new Error(
129
+ "run() with pre-seeded messages is no longer supported. Use createConversation/sendMessage."
130
+ );
131
+ }
132
+ const conversation = await this.createConversation({
133
+ title: input.task
134
+ });
135
+ return await this.sendMessage(conversation.conversationId, input.task, input.parameters);
136
+ }
137
+ async continue(input) {
138
+ return await this.sendMessage(input.runId, input.message, input.parameters);
139
+ }
140
+ conversation(initialRunId) {
141
+ let runId = initialRunId;
142
+ return {
143
+ send: async (message, parameters) => {
144
+ if (!runId) {
145
+ const initialConversation = await this.createConversation({ title: message });
146
+ const initial = await this.sendMessage(
147
+ initialConversation.conversationId,
148
+ message,
149
+ parameters
150
+ );
151
+ runId = initialConversation.conversationId;
152
+ return initial;
153
+ }
154
+ const next = await this.continue({ runId, message, parameters });
155
+ return next;
156
+ }
157
+ };
158
+ }
159
+ async *stream(input) {
160
+ if ((input.messages?.length ?? 0) > 0) {
161
+ throw new Error(
162
+ "stream() with pre-seeded messages is no longer supported. Use conversation APIs directly."
163
+ );
164
+ }
165
+ const conversation = await this.createConversation({ title: input.task });
166
+ const response = await this.fetchImpl(
167
+ `${this.baseUrl}/api/conversations/${encodeURIComponent(conversation.conversationId)}/messages`,
168
+ {
169
+ method: "POST",
170
+ headers: this.headers(),
171
+ body: JSON.stringify({ message: input.task, parameters: input.parameters })
172
+ }
173
+ );
174
+ if (!response.ok || !response.body) {
175
+ throw new Error(`Streaming request failed: HTTP ${response.status}`);
176
+ }
177
+ const reader = response.body.getReader();
178
+ const decoder = new TextDecoder();
179
+ let buffer = "";
180
+ while (true) {
181
+ const { done, value } = await reader.read();
182
+ if (done) {
183
+ break;
184
+ }
185
+ buffer += decoder.decode(value, { stream: true });
186
+ const frames = buffer.split("\n\n");
187
+ buffer = frames.pop() ?? "";
188
+ for (const frame of frames) {
189
+ const lines = frame.split("\n").map((line) => line.trim()).filter(Boolean);
190
+ const eventType = lines.find((line) => line.startsWith("event:"));
191
+ const dataLine = lines.find((line) => line.startsWith("data:"));
192
+ if (!eventType || !dataLine) {
193
+ continue;
194
+ }
195
+ const payload = JSON.parse(dataLine.slice("data:".length).trim());
196
+ yield payload;
197
+ }
198
+ }
199
+ }
200
+ };
201
+ export {
202
+ AgentClient
203
+ };
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@poncho-ai/client",
3
+ "version": "0.2.0",
4
+ "description": "TypeScript client for calling deployed Poncho agents",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/latitude-dev/poncho-ai.git",
8
+ "directory": "packages/client"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "type": "module",
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ }
21
+ },
22
+ "dependencies": {
23
+ "@poncho-ai/sdk": "0.2.0"
24
+ },
25
+ "devDependencies": {
26
+ "tsup": "^8.0.0",
27
+ "vitest": "^1.4.0"
28
+ },
29
+ "keywords": [
30
+ "ai",
31
+ "agent",
32
+ "client",
33
+ "sdk"
34
+ ],
35
+ "license": "MIT",
36
+ "scripts": {
37
+ "build": "tsup src/index.ts --format esm --dts",
38
+ "dev": "tsup src/index.ts --format esm --dts --watch",
39
+ "test": "vitest",
40
+ "lint": "eslint src/"
41
+ }
42
+ }
package/src/index.ts ADDED
@@ -0,0 +1,267 @@
1
+ import type { AgentEvent, RunInput, RunResult } from "@poncho-ai/sdk";
2
+
3
+ export interface AgentClientOptions {
4
+ url: string;
5
+ apiKey?: string;
6
+ fetchImpl?: typeof fetch;
7
+ }
8
+
9
+ export interface SyncRunResponse {
10
+ runId: string;
11
+ status: RunResult["status"];
12
+ result: RunResult;
13
+ }
14
+
15
+ export interface ContinueInput {
16
+ runId: string;
17
+ message: string;
18
+ parameters?: Record<string, unknown>;
19
+ }
20
+
21
+ export interface ConversationSummary {
22
+ conversationId: string;
23
+ title: string;
24
+ runtimeRunId?: string;
25
+ ownerId: string;
26
+ tenantId: string | null;
27
+ createdAt: number;
28
+ updatedAt: number;
29
+ messageCount: number;
30
+ }
31
+
32
+ export interface ConversationRecord extends Omit<ConversationSummary, "messageCount"> {
33
+ messages: Array<{ role: "user" | "assistant"; content: string }>;
34
+ }
35
+
36
+ const trimTrailingSlash = (value: string): string => value.replace(/\/+$/, "");
37
+
38
+ export class AgentClient {
39
+ private readonly baseUrl: string;
40
+ private readonly apiKey?: string;
41
+ private readonly fetchImpl: typeof fetch;
42
+
43
+ constructor(options: AgentClientOptions) {
44
+ this.baseUrl = trimTrailingSlash(options.url);
45
+ this.apiKey = options.apiKey;
46
+ this.fetchImpl = options.fetchImpl ?? fetch;
47
+ }
48
+
49
+ private headers(): HeadersInit {
50
+ const headers: Record<string, string> = {
51
+ "Content-Type": "application/json",
52
+ };
53
+ if (this.apiKey) {
54
+ headers.Authorization = `Bearer ${this.apiKey}`;
55
+ }
56
+ return headers;
57
+ }
58
+
59
+ private async parseSse(response: Response): Promise<AgentEvent[]> {
60
+ if (!response.body) {
61
+ throw new Error("Missing response body");
62
+ }
63
+ const reader = response.body.getReader();
64
+ const decoder = new TextDecoder();
65
+ let buffer = "";
66
+ const events: AgentEvent[] = [];
67
+ while (true) {
68
+ const { done, value } = await reader.read();
69
+ if (done) {
70
+ break;
71
+ }
72
+ buffer += decoder.decode(value, { stream: true });
73
+ const frames = buffer.split("\n\n");
74
+ buffer = frames.pop() ?? "";
75
+ for (const frame of frames) {
76
+ const lines = frame
77
+ .split("\n")
78
+ .map((line) => line.trim())
79
+ .filter(Boolean);
80
+ const dataLine = lines.find((line) => line.startsWith("data:"));
81
+ if (!dataLine) {
82
+ continue;
83
+ }
84
+ const payload = JSON.parse(dataLine.slice("data:".length).trim()) as AgentEvent;
85
+ events.push(payload);
86
+ }
87
+ }
88
+ return events;
89
+ }
90
+
91
+ async listConversations(): Promise<ConversationSummary[]> {
92
+ const response = await this.fetchImpl(`${this.baseUrl}/api/conversations`, {
93
+ method: "GET",
94
+ headers: this.headers(),
95
+ });
96
+ if (!response.ok) {
97
+ throw new Error(`List conversations failed: HTTP ${response.status}`);
98
+ }
99
+ const payload = (await response.json()) as { conversations: ConversationSummary[] };
100
+ return payload.conversations;
101
+ }
102
+
103
+ async createConversation(input?: { title?: string }): Promise<ConversationRecord> {
104
+ const response = await this.fetchImpl(`${this.baseUrl}/api/conversations`, {
105
+ method: "POST",
106
+ headers: this.headers(),
107
+ body: JSON.stringify(input ?? {}),
108
+ });
109
+ if (!response.ok) {
110
+ throw new Error(`Create conversation failed: HTTP ${response.status}`);
111
+ }
112
+ const payload = (await response.json()) as { conversation: ConversationRecord };
113
+ return payload.conversation;
114
+ }
115
+
116
+ async getConversation(conversationId: string): Promise<ConversationRecord> {
117
+ const response = await this.fetchImpl(
118
+ `${this.baseUrl}/api/conversations/${encodeURIComponent(conversationId)}`,
119
+ {
120
+ method: "GET",
121
+ headers: this.headers(),
122
+ },
123
+ );
124
+ if (!response.ok) {
125
+ throw new Error(`Get conversation failed: HTTP ${response.status}`);
126
+ }
127
+ const payload = (await response.json()) as { conversation: ConversationRecord };
128
+ return payload.conversation;
129
+ }
130
+
131
+ async deleteConversation(conversationId: string): Promise<void> {
132
+ const response = await this.fetchImpl(
133
+ `${this.baseUrl}/api/conversations/${encodeURIComponent(conversationId)}`,
134
+ {
135
+ method: "DELETE",
136
+ headers: this.headers(),
137
+ },
138
+ );
139
+ if (!response.ok) {
140
+ throw new Error(`Delete conversation failed: HTTP ${response.status}`);
141
+ }
142
+ }
143
+
144
+ async sendMessage(
145
+ conversationId: string,
146
+ message: string,
147
+ parameters?: Record<string, unknown>,
148
+ ): Promise<SyncRunResponse> {
149
+ const response = await this.fetchImpl(
150
+ `${this.baseUrl}/api/conversations/${encodeURIComponent(conversationId)}/messages`,
151
+ {
152
+ method: "POST",
153
+ headers: this.headers(),
154
+ body: JSON.stringify({ message, parameters }),
155
+ },
156
+ );
157
+ if (!response.ok) {
158
+ throw new Error(`Send message failed: HTTP ${response.status}`);
159
+ }
160
+ const events = await this.parseSse(response);
161
+ const runStarted = events.find(
162
+ (event): event is Extract<AgentEvent, { type: "run:started" }> =>
163
+ event.type === "run:started",
164
+ );
165
+ const completed = events.find(
166
+ (event): event is Extract<AgentEvent, { type: "run:completed" }> =>
167
+ event.type === "run:completed",
168
+ );
169
+ if (!completed) {
170
+ throw new Error("Send message failed: missing run:completed event");
171
+ }
172
+ return {
173
+ runId: runStarted?.runId ?? completed.runId,
174
+ status: completed.result.status,
175
+ result: completed.result,
176
+ };
177
+ }
178
+
179
+ async run(input: RunInput): Promise<SyncRunResponse> {
180
+ if ((input.messages?.length ?? 0) > 0) {
181
+ throw new Error(
182
+ "run() with pre-seeded messages is no longer supported. Use createConversation/sendMessage.",
183
+ );
184
+ }
185
+ const conversation = await this.createConversation({
186
+ title: input.task,
187
+ });
188
+ return await this.sendMessage(conversation.conversationId, input.task, input.parameters);
189
+ }
190
+
191
+ async continue(input: ContinueInput): Promise<SyncRunResponse> {
192
+ return await this.sendMessage(input.runId, input.message, input.parameters);
193
+ }
194
+
195
+ conversation(initialRunId?: string): {
196
+ send: (message: string, parameters?: Record<string, unknown>) => Promise<SyncRunResponse>;
197
+ } {
198
+ let runId = initialRunId;
199
+ return {
200
+ send: async (message: string, parameters?: Record<string, unknown>) => {
201
+ if (!runId) {
202
+ const initialConversation = await this.createConversation({ title: message });
203
+ const initial = await this.sendMessage(
204
+ initialConversation.conversationId,
205
+ message,
206
+ parameters,
207
+ );
208
+ runId = initialConversation.conversationId;
209
+ return initial;
210
+ }
211
+ const next = await this.continue({ runId, message, parameters });
212
+ return next;
213
+ },
214
+ };
215
+ }
216
+
217
+ async *stream(input: RunInput): AsyncGenerator<AgentEvent> {
218
+ if ((input.messages?.length ?? 0) > 0) {
219
+ throw new Error(
220
+ "stream() with pre-seeded messages is no longer supported. Use conversation APIs directly.",
221
+ );
222
+ }
223
+ const conversation = await this.createConversation({ title: input.task });
224
+ const response = await this.fetchImpl(
225
+ `${this.baseUrl}/api/conversations/${encodeURIComponent(conversation.conversationId)}/messages`,
226
+ {
227
+ method: "POST",
228
+ headers: this.headers(),
229
+ body: JSON.stringify({ message: input.task, parameters: input.parameters }),
230
+ });
231
+
232
+ if (!response.ok || !response.body) {
233
+ throw new Error(`Streaming request failed: HTTP ${response.status}`);
234
+ }
235
+
236
+ const reader = response.body.getReader();
237
+ const decoder = new TextDecoder();
238
+ let buffer = "";
239
+
240
+ while (true) {
241
+ const { done, value } = await reader.read();
242
+ if (done) {
243
+ break;
244
+ }
245
+
246
+ buffer += decoder.decode(value, { stream: true });
247
+ const frames = buffer.split("\n\n");
248
+ buffer = frames.pop() ?? "";
249
+
250
+ for (const frame of frames) {
251
+ const lines = frame
252
+ .split("\n")
253
+ .map((line) => line.trim())
254
+ .filter(Boolean);
255
+
256
+ const eventType = lines.find((line) => line.startsWith("event:"));
257
+ const dataLine = lines.find((line) => line.startsWith("data:"));
258
+ if (!eventType || !dataLine) {
259
+ continue;
260
+ }
261
+
262
+ const payload = JSON.parse(dataLine.slice("data:".length).trim()) as AgentEvent;
263
+ yield payload;
264
+ }
265
+ }
266
+ }
267
+ }
@@ -0,0 +1,92 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { AgentClient } from "../src/index.js";
3
+
4
+ describe("AgentClient", () => {
5
+ const sseResponse = (): Response =>
6
+ new Response(
7
+ `event: run:started\n` +
8
+ `data: {"type":"run:started","runId":"run_1","agentId":"test-agent"}\n\n` +
9
+ `event: run:completed\n` +
10
+ `data: {"type":"run:completed","runId":"run_1","result":{"status":"completed","response":"ok","steps":1,"tokens":{"input":1,"output":1,"cached":0},"duration":1}}\n\n`,
11
+ {
12
+ status: 200,
13
+ headers: { "Content-Type": "text/event-stream" },
14
+ },
15
+ );
16
+
17
+ it("runs a message through conversation APIs", async () => {
18
+ let call = 0;
19
+ const client = new AgentClient({
20
+ url: "http://localhost:3000",
21
+ fetchImpl: async () => {
22
+ call += 1;
23
+ if (call === 1) {
24
+ return new Response(
25
+ JSON.stringify({
26
+ conversation: {
27
+ conversationId: "conv_1",
28
+ title: "hello",
29
+ ownerId: "local-owner",
30
+ tenantId: null,
31
+ createdAt: Date.now(),
32
+ updatedAt: Date.now(),
33
+ messages: [],
34
+ },
35
+ }),
36
+ {
37
+ status: 201,
38
+ headers: { "Content-Type": "application/json" },
39
+ },
40
+ );
41
+ }
42
+ return sseResponse();
43
+ },
44
+ });
45
+
46
+ const result = await client.run({ task: "hello" });
47
+ expect(result.runId).toBe("run_1");
48
+ expect(result.result.response).toBe("ok");
49
+ });
50
+
51
+ it("supports continue() via conversation message route", async () => {
52
+ const client = new AgentClient({
53
+ url: "http://localhost:3000",
54
+ fetchImpl: async () => sseResponse(),
55
+ });
56
+
57
+ const result = await client.continue({ runId: "conv_1", message: "next" });
58
+ expect(result.runId).toBe("run_1");
59
+ expect(result.result.response).toBe("ok");
60
+ });
61
+
62
+ it("lists conversations", async () => {
63
+ const client = new AgentClient({
64
+ url: "http://localhost:3000",
65
+ fetchImpl: async () =>
66
+ new Response(
67
+ JSON.stringify({
68
+ conversations: [
69
+ {
70
+ conversationId: "conv_1",
71
+ title: "Example",
72
+ runtimeRunId: "run_1",
73
+ ownerId: "local-owner",
74
+ tenantId: null,
75
+ createdAt: Date.now(),
76
+ updatedAt: Date.now(),
77
+ messageCount: 2,
78
+ },
79
+ ],
80
+ }),
81
+ {
82
+ status: 200,
83
+ headers: { "Content-Type": "application/json" },
84
+ },
85
+ ),
86
+ });
87
+
88
+ const results = await client.listConversations();
89
+ expect(results).toHaveLength(1);
90
+ expect(results[0]?.conversationId).toBe("conv_1");
91
+ });
92
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./dist"
6
+ },
7
+ "include": ["src/**/*.ts", "test/**/*.ts"]
8
+ }