@classic-homes/agent-client 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +70 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +156 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +47 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/stream.d.ts +27 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +69 -0
- package/dist/stream.js.map +1 -0
- package/package.json +31 -3
- package/README.md +0 -3
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { AgentsResponse, AuthExchangeResponse, ConversationListResponse, DevicePollResponse, DeviceStartResponse, MeResponse, SessionResponse, type AuthExchangeRequest, type ChatEvent, type ChatRequest, type FileRef } from '@classic-homes/agent-contracts';
|
|
2
|
+
import { type StreamOutcome } from './stream.js';
|
|
3
|
+
export interface AgentApiClientOptions {
|
|
4
|
+
/** Origin of the API; empty for same-origin (the Office pane). No trailing slash. */
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
fetch?: typeof fetch;
|
|
7
|
+
/**
|
|
8
|
+
* Supplies the bearer for each request (the Classic App passes its CHAPI token here). When
|
|
9
|
+
* absent, the client sends the in-memory session token obtained from sign-in.
|
|
10
|
+
*/
|
|
11
|
+
getToken?: () => Promise<string | undefined> | string | undefined;
|
|
12
|
+
/** Default `same-origin`; the Classic App uses `omit` (bearer only, no cookies). */
|
|
13
|
+
credentials?: 'omit' | 'same-origin' | 'include';
|
|
14
|
+
}
|
|
15
|
+
export interface ConversationDetail {
|
|
16
|
+
conversation: ConversationListResponse['conversations'][number];
|
|
17
|
+
messages: Array<{
|
|
18
|
+
id: string;
|
|
19
|
+
seq: number;
|
|
20
|
+
role: 'user' | 'assistant';
|
|
21
|
+
agent?: string;
|
|
22
|
+
content: Array<{
|
|
23
|
+
type: 'text';
|
|
24
|
+
text: string;
|
|
25
|
+
}>;
|
|
26
|
+
createdAt: number;
|
|
27
|
+
}>;
|
|
28
|
+
files: FileRef[];
|
|
29
|
+
}
|
|
30
|
+
export interface AgentApiClient {
|
|
31
|
+
getSession(): Promise<SessionResponse>;
|
|
32
|
+
getMe(): Promise<MeResponse>;
|
|
33
|
+
listAgents(): Promise<AgentsResponse>;
|
|
34
|
+
streamChat(request: ChatRequest, options: {
|
|
35
|
+
signal: AbortSignal;
|
|
36
|
+
onEvent: (event: ChatEvent) => void;
|
|
37
|
+
}): Promise<StreamOutcome>;
|
|
38
|
+
/** Re-attach to a turn still running (or recently finished), replaying events after `since`. */
|
|
39
|
+
resumeTurn(conversationId: string, requestId: string, since: number, options: {
|
|
40
|
+
signal: AbortSignal;
|
|
41
|
+
onEvent: (event: ChatEvent) => void;
|
|
42
|
+
}): Promise<StreamOutcome>;
|
|
43
|
+
/** Best-effort server-side cancel; aborting the stream also cancels. */
|
|
44
|
+
cancel(requestId: string): Promise<void>;
|
|
45
|
+
listConversations(params?: {
|
|
46
|
+
cursor?: string;
|
|
47
|
+
q?: string;
|
|
48
|
+
limit?: number;
|
|
49
|
+
}): Promise<ConversationListResponse>;
|
|
50
|
+
getConversation(conversationId: string): Promise<ConversationDetail>;
|
|
51
|
+
renameConversation(conversationId: string, title: string): Promise<void>;
|
|
52
|
+
deleteConversation(conversationId: string): Promise<void>;
|
|
53
|
+
uploadFile(file: Blob, name: string, options?: {
|
|
54
|
+
signal?: AbortSignal;
|
|
55
|
+
}): Promise<FileRef>;
|
|
56
|
+
/** Absolute or same-origin URL to download a file; requires the bearer, so prefer `fetch` + blob for display. */
|
|
57
|
+
fileUrl(fileId: string): string;
|
|
58
|
+
/** Office sign-in: redeem a single-use handoff code (dialog flow). */
|
|
59
|
+
exchangeHandoff(request: AuthExchangeRequest): Promise<AuthExchangeResponse>;
|
|
60
|
+
/** Office sign-in: device flow. `devicePoll` stores the session token when authorized. */
|
|
61
|
+
deviceStart(): Promise<DeviceStartResponse>;
|
|
62
|
+
devicePoll(loginId: string): Promise<DevicePollResponse>;
|
|
63
|
+
/** End the session and forget the in-memory token. */
|
|
64
|
+
logout(): Promise<void>;
|
|
65
|
+
hasSessionToken(): boolean;
|
|
66
|
+
/** Called after a request was refused with 401 (the in-memory token is already cleared). */
|
|
67
|
+
onUnauthenticated(listener: () => void): () => void;
|
|
68
|
+
}
|
|
69
|
+
export declare function createAgentApiClient(options?: AgentApiClientOptions): AgentApiClient;
|
|
70
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { AgentsResponse, API_PATHS, AUTH_PATHS, AuthExchangeResponse, ConversationListResponse, DEVICE_AUTH_PATHS, DevicePollResponse, DeviceStartResponse, FileUploadResponse, MeResponse, SessionResponse, cancelPath, conversationEventsPath, conversationPath, filePath, } from '@classic-homes/agent-contracts';
|
|
2
|
+
import { ApiRequestError, errorFromResponse, isAbort } from './errors.js';
|
|
3
|
+
import { readChatStream } from './stream.js';
|
|
4
|
+
export function createAgentApiClient(options = {}) {
|
|
5
|
+
const fetchImpl = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
6
|
+
const baseUrl = (options.baseUrl ?? '').replace(/\/$/, '');
|
|
7
|
+
const credentials = options.credentials ?? 'same-origin';
|
|
8
|
+
/** Session bearer, memory only: lost on reload, never written to storage. */
|
|
9
|
+
let sessionToken;
|
|
10
|
+
const unauthenticatedListeners = new Set();
|
|
11
|
+
async function bearer() {
|
|
12
|
+
if (options.getToken)
|
|
13
|
+
return (await options.getToken()) ?? undefined;
|
|
14
|
+
return sessionToken;
|
|
15
|
+
}
|
|
16
|
+
async function request(path, init) {
|
|
17
|
+
const headers = new Headers(init?.headers);
|
|
18
|
+
const token = await bearer();
|
|
19
|
+
if (token)
|
|
20
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
21
|
+
let response;
|
|
22
|
+
try {
|
|
23
|
+
response = await fetchImpl(`${baseUrl}${path}`, { ...init, credentials, headers });
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (isAbort(error, init?.signal ?? undefined))
|
|
27
|
+
throw error;
|
|
28
|
+
throw new ApiRequestError('network', 'Cannot reach the Classic Agent backend.');
|
|
29
|
+
}
|
|
30
|
+
if (response.status === 401 && path.startsWith('/api/')) {
|
|
31
|
+
sessionToken = undefined;
|
|
32
|
+
for (const listener of [...unauthenticatedListeners])
|
|
33
|
+
listener();
|
|
34
|
+
}
|
|
35
|
+
return response;
|
|
36
|
+
}
|
|
37
|
+
async function json(path, schema, init) {
|
|
38
|
+
const response = await request(path, init);
|
|
39
|
+
if (!response.ok)
|
|
40
|
+
throw await errorFromResponse(response);
|
|
41
|
+
try {
|
|
42
|
+
return schema.parse(await response.json());
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
throw new ApiRequestError('protocol', `Unexpected response from ${path}.`, response.status);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function stream(path, init, requestId, since, streamOptions) {
|
|
49
|
+
let response;
|
|
50
|
+
try {
|
|
51
|
+
response = await request(path, {
|
|
52
|
+
...init,
|
|
53
|
+
headers: { ...init.headers, Accept: 'text/event-stream' },
|
|
54
|
+
signal: streamOptions.signal,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (isAbort(error, streamOptions.signal))
|
|
59
|
+
return { kind: 'aborted' };
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
if (!response.ok)
|
|
63
|
+
throw await errorFromResponse(response);
|
|
64
|
+
return readChatStream(response, { ...streamOptions, requestId, since });
|
|
65
|
+
}
|
|
66
|
+
const JSON_HEADERS = { 'Content-Type': 'application/json', Accept: 'application/json' };
|
|
67
|
+
const ACCEPT_JSON = { Accept: 'application/json' };
|
|
68
|
+
return {
|
|
69
|
+
getSession: () => json(API_PATHS.session, SessionResponse, { headers: ACCEPT_JSON }),
|
|
70
|
+
getMe: () => json(API_PATHS.me, MeResponse, { headers: ACCEPT_JSON }),
|
|
71
|
+
listAgents: () => json(API_PATHS.agents, AgentsResponse, { headers: ACCEPT_JSON }),
|
|
72
|
+
streamChat: (chatRequest, streamOptions) => stream(API_PATHS.chat, {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: { 'Content-Type': 'application/json' },
|
|
75
|
+
body: JSON.stringify(chatRequest),
|
|
76
|
+
}, chatRequest.requestId, undefined, streamOptions),
|
|
77
|
+
resumeTurn: (conversationId, requestId, since, streamOptions) => stream(`${conversationEventsPath(conversationId, since)}&requestId=${encodeURIComponent(requestId)}`, { method: 'GET' }, requestId, since, streamOptions),
|
|
78
|
+
async cancel(requestId) {
|
|
79
|
+
try {
|
|
80
|
+
await request(cancelPath(requestId), { method: 'POST' });
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Best effort: the aborted stream also cancels upstream.
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
listConversations(params = {}) {
|
|
87
|
+
const query = new URLSearchParams();
|
|
88
|
+
if (params.cursor)
|
|
89
|
+
query.set('cursor', params.cursor);
|
|
90
|
+
if (params.q)
|
|
91
|
+
query.set('q', params.q);
|
|
92
|
+
if (params.limit)
|
|
93
|
+
query.set('limit', String(params.limit));
|
|
94
|
+
const suffix = query.size ? `?${query}` : '';
|
|
95
|
+
return json(`${API_PATHS.conversations}${suffix}`, ConversationListResponse, {
|
|
96
|
+
headers: ACCEPT_JSON,
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
getConversation: (conversationId) => json(conversationPath(conversationId), { parse: (v) => v }, { headers: ACCEPT_JSON }),
|
|
100
|
+
async renameConversation(conversationId, title) {
|
|
101
|
+
await json(conversationPath(conversationId), { parse: (v) => v }, { method: 'PATCH', headers: JSON_HEADERS, body: JSON.stringify({ title }) });
|
|
102
|
+
},
|
|
103
|
+
async deleteConversation(conversationId) {
|
|
104
|
+
const response = await request(conversationPath(conversationId), { method: 'DELETE' });
|
|
105
|
+
if (!response.ok)
|
|
106
|
+
throw await errorFromResponse(response);
|
|
107
|
+
},
|
|
108
|
+
async uploadFile(file, name, uploadOptions = {}) {
|
|
109
|
+
const form = new FormData();
|
|
110
|
+
form.append('file', file, name);
|
|
111
|
+
const result = await json(API_PATHS.files, FileUploadResponse, {
|
|
112
|
+
method: 'POST',
|
|
113
|
+
body: form,
|
|
114
|
+
signal: uploadOptions.signal ?? null,
|
|
115
|
+
});
|
|
116
|
+
return result.file;
|
|
117
|
+
},
|
|
118
|
+
fileUrl: (fileId) => `${baseUrl}${filePath(fileId)}`,
|
|
119
|
+
async exchangeHandoff(exchange) {
|
|
120
|
+
const result = await json(AUTH_PATHS.exchange, AuthExchangeResponse, {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
headers: JSON_HEADERS,
|
|
123
|
+
body: JSON.stringify(exchange),
|
|
124
|
+
});
|
|
125
|
+
sessionToken = result.sessionToken;
|
|
126
|
+
return result;
|
|
127
|
+
},
|
|
128
|
+
deviceStart: () => json(DEVICE_AUTH_PATHS.start, DeviceStartResponse, { method: 'POST', headers: ACCEPT_JSON }),
|
|
129
|
+
async devicePoll(loginId) {
|
|
130
|
+
const result = await json(DEVICE_AUTH_PATHS.poll, DevicePollResponse, {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers: JSON_HEADERS,
|
|
133
|
+
body: JSON.stringify({ loginId }),
|
|
134
|
+
});
|
|
135
|
+
if (result.status === 'authorized')
|
|
136
|
+
sessionToken = result.sessionToken;
|
|
137
|
+
return result;
|
|
138
|
+
},
|
|
139
|
+
async logout() {
|
|
140
|
+
try {
|
|
141
|
+
const response = await request(AUTH_PATHS.logout, { method: 'POST' });
|
|
142
|
+
if (!response.ok)
|
|
143
|
+
throw await errorFromResponse(response);
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
sessionToken = undefined;
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
hasSessionToken: () => sessionToken !== undefined,
|
|
150
|
+
onUnauthenticated(listener) {
|
|
151
|
+
unauthenticatedListeners.add(listener);
|
|
152
|
+
return () => unauthenticatedListeners.delete(listener);
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ErrorCode } from '@classic-homes/agent-contracts';
|
|
2
|
+
/** Failure codes a client can show: contract error codes plus transport problems. */
|
|
3
|
+
export type ClientErrorCode = ErrorCode | 'network' | 'protocol';
|
|
4
|
+
export declare class ApiRequestError extends Error {
|
|
5
|
+
readonly code: ClientErrorCode;
|
|
6
|
+
readonly status: number | undefined;
|
|
7
|
+
readonly retryable: boolean;
|
|
8
|
+
constructor(code: ClientErrorCode, message: string, status?: number);
|
|
9
|
+
}
|
|
10
|
+
export declare function errorFromResponse(response: Response): Promise<ApiRequestError>;
|
|
11
|
+
export declare function isAbort(error: unknown, signal?: AbortSignal): boolean;
|
|
12
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAE1E,qFAAqF;AACrF,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;AAEjE,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;gBAChB,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAOpE;AAcD,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,eAAe,CAAC,CAqBpF;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAErE"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ApiError } from '@classic-homes/agent-contracts';
|
|
2
|
+
export class ApiRequestError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
status;
|
|
5
|
+
retryable;
|
|
6
|
+
constructor(code, message, status) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'ApiRequestError';
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.retryable = ['network', 'rate_limited', 'upstream_unavailable', 'timeout'].includes(code);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const STATUS_CODES = {
|
|
15
|
+
400: 'bad_request',
|
|
16
|
+
401: 'unauthenticated',
|
|
17
|
+
403: 'forbidden',
|
|
18
|
+
404: 'not_found',
|
|
19
|
+
413: 'message_too_large',
|
|
20
|
+
429: 'rate_limited',
|
|
21
|
+
502: 'upstream_unavailable',
|
|
22
|
+
503: 'upstream_unavailable',
|
|
23
|
+
504: 'timeout',
|
|
24
|
+
};
|
|
25
|
+
export async function errorFromResponse(response) {
|
|
26
|
+
let body;
|
|
27
|
+
try {
|
|
28
|
+
body = await response.json();
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
body = undefined;
|
|
32
|
+
}
|
|
33
|
+
const parsed = ApiError.safeParse(body);
|
|
34
|
+
if (parsed.success) {
|
|
35
|
+
return new ApiRequestError(parsed.data.error.code, parsed.data.error.message, response.status);
|
|
36
|
+
}
|
|
37
|
+
// No contract body: most likely a proxy could not reach the backend.
|
|
38
|
+
if (response.status >= 500 && response.status !== 503 && response.status !== 504) {
|
|
39
|
+
return new ApiRequestError('network', `The Classic Agent backend is unavailable (HTTP ${response.status}).`, response.status);
|
|
40
|
+
}
|
|
41
|
+
const code = STATUS_CODES[response.status] ?? 'internal';
|
|
42
|
+
return new ApiRequestError(code, `Request failed with HTTP ${response.status}.`, response.status);
|
|
43
|
+
}
|
|
44
|
+
export function isAbort(error, signal) {
|
|
45
|
+
return !!signal?.aborted || (error instanceof DOMException && error.name === 'AbortError');
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAkB,MAAM,gCAAgC,CAAC;AAK1E,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAC/B,IAAI,CAAkB;IACtB,MAAM,CAAqB;IAC3B,SAAS,CAAU;IAC5B,YAAY,IAAqB,EAAE,OAAe,EAAE,MAAe;QACjE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,SAAS,GAAG,CAAC,SAAS,EAAE,cAAc,EAAE,sBAAsB,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjG,CAAC;CACF;AAED,MAAM,YAAY,GAA8B;IAC9C,GAAG,EAAE,aAAa;IAClB,GAAG,EAAE,iBAAiB;IACtB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,mBAAmB;IACxB,GAAG,EAAE,cAAc;IACnB,GAAG,EAAE,sBAAsB;IAC3B,GAAG,EAAE,sBAAsB;IAC3B,GAAG,EAAE,SAAS;CACf,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,QAAkB;IACxD,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,GAAG,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,OAAO,IAAI,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACjG,CAAC;IACD,qEAAqE;IACrE,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QACjF,OAAO,IAAI,eAAe,CACxB,SAAS,EACT,kDAAkD,QAAQ,CAAC,MAAM,IAAI,EACrE,QAAQ,CAAC,MAAM,CAChB,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,UAAU,CAAC;IACzD,OAAO,IAAI,eAAe,CAAC,IAAI,EAAE,4BAA4B,QAAQ,CAAC,MAAM,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;AACpG,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAc,EAAE,MAAoB;IAC1D,OAAO,CAAC,CAAC,MAAM,EAAE,OAAO,IAAI,CAAC,KAAK,YAAY,YAAY,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC;AAC7F,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
|
package/dist/stream.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ChatEvent, type DoneEvent } from '@classic-homes/agent-contracts';
|
|
2
|
+
export type StreamOutcome = {
|
|
3
|
+
kind: 'done';
|
|
4
|
+
event: DoneEvent;
|
|
5
|
+
}
|
|
6
|
+
/** The connection closed before a `done` event (possibly mid-frame). */
|
|
7
|
+
| {
|
|
8
|
+
kind: 'ended';
|
|
9
|
+
partialFrame: boolean;
|
|
10
|
+
} | {
|
|
11
|
+
kind: 'aborted';
|
|
12
|
+
};
|
|
13
|
+
export interface StreamOptions {
|
|
14
|
+
signal: AbortSignal;
|
|
15
|
+
onEvent: (event: ChatEvent) => void;
|
|
16
|
+
/** The request id every event must carry. */
|
|
17
|
+
requestId: string;
|
|
18
|
+
/** When resuming, the last `seq` already seen; events at or below it are rejected. */
|
|
19
|
+
since?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Read an event stream and enforce the contract: one request id, a leading `start` (unless
|
|
23
|
+
* resuming), strictly increasing `seq`, and a single `done`. Unknown event types keep the
|
|
24
|
+
* ordering checks and are otherwise ignored.
|
|
25
|
+
*/
|
|
26
|
+
export declare function readChatStream(response: Response, options: StreamOptions): Promise<StreamOutcome>;
|
|
27
|
+
//# sourceMappingURL=stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,gCAAgC,CAAC;AAGxC,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,CAAA;CAAE;AACpC,wEAAwE;GACtE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAExB,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACpC,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,aAAa,CAAC,CAwDxB"}
|
package/dist/stream.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { SseDecoder, isKnownEvent, } from '@classic-homes/agent-contracts';
|
|
2
|
+
import { ApiRequestError, isAbort } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* Read an event stream and enforce the contract: one request id, a leading `start` (unless
|
|
5
|
+
* resuming), strictly increasing `seq`, and a single `done`. Unknown event types keep the
|
|
6
|
+
* ordering checks and are otherwise ignored.
|
|
7
|
+
*/
|
|
8
|
+
export async function readChatStream(response, options) {
|
|
9
|
+
const { signal, onEvent } = options;
|
|
10
|
+
const type = response.headers.get('content-type') ?? '';
|
|
11
|
+
if (!type.includes('text/event-stream') || !response.body) {
|
|
12
|
+
throw new ApiRequestError('protocol', 'The backend did not return an event stream.');
|
|
13
|
+
}
|
|
14
|
+
const reader = response.body.getReader();
|
|
15
|
+
const text = new TextDecoder();
|
|
16
|
+
const decoder = new SseDecoder();
|
|
17
|
+
let lastSeq = options.since ?? -1;
|
|
18
|
+
const resuming = options.since !== undefined;
|
|
19
|
+
const accept = (events) => {
|
|
20
|
+
for (const event of events) {
|
|
21
|
+
if (event.requestId !== options.requestId) {
|
|
22
|
+
throw new ApiRequestError('protocol', 'The stream belongs to a different request.');
|
|
23
|
+
}
|
|
24
|
+
if (!resuming && lastSeq === -1 && event.type !== 'start') {
|
|
25
|
+
throw new ApiRequestError('protocol', 'The stream did not begin with a start event.');
|
|
26
|
+
}
|
|
27
|
+
if (event.seq <= lastSeq) {
|
|
28
|
+
throw new ApiRequestError('protocol', 'The stream events arrived out of order.');
|
|
29
|
+
}
|
|
30
|
+
lastSeq = event.seq;
|
|
31
|
+
if (!isKnownEvent(event))
|
|
32
|
+
continue;
|
|
33
|
+
onEvent(event);
|
|
34
|
+
if (event.type === 'done')
|
|
35
|
+
return event;
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
};
|
|
39
|
+
try {
|
|
40
|
+
for (;;) {
|
|
41
|
+
const { value, done } = await reader.read();
|
|
42
|
+
if (done)
|
|
43
|
+
break;
|
|
44
|
+
let events;
|
|
45
|
+
try {
|
|
46
|
+
events = decoder.push(text.decode(value, { stream: true }));
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
throw new ApiRequestError('protocol', 'The backend sent an invalid stream event.');
|
|
50
|
+
}
|
|
51
|
+
const finished = accept(events);
|
|
52
|
+
if (finished) {
|
|
53
|
+
void reader.cancel().catch(() => { });
|
|
54
|
+
return { kind: 'done', event: finished };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (isAbort(error, signal))
|
|
60
|
+
return { kind: 'aborted' };
|
|
61
|
+
if (error instanceof ApiRequestError) {
|
|
62
|
+
void reader.cancel().catch(() => { });
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
return { kind: 'ended', partialFrame: decoder.pending };
|
|
66
|
+
}
|
|
67
|
+
return { kind: 'ended', partialFrame: decoder.pending };
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classic-homes/agent-client",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Framework-free client for the Classic Agent API: sessions, streaming chat with ordering checks, conversations and files. Shared by the Office task pane and the Classic App.",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"source": "./src/index.ts",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@classic-homes/agent-contracts": "0.1.1"
|
|
19
|
+
},
|
|
20
|
+
"license": "UNLICENSED",
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"provenance": true
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/Classic-Homes/classic-agent.git",
|
|
28
|
+
"directory": "packages/agent-client"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc -p tsconfig.build.json",
|
|
32
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
33
|
+
}
|
|
6
34
|
}
|
package/README.md
DELETED