@abloatai/ablo 0.9.0 → 0.9.2
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/AGENTS.md +84 -0
- package/CHANGELOG.md +40 -0
- package/README.md +15 -7
- package/dist/BaseSyncedStore.d.ts +10 -0
- package/dist/BaseSyncedStore.js +26 -0
- package/dist/SyncClient.d.ts +12 -0
- package/dist/SyncClient.js +15 -0
- package/dist/agent/index.js +1 -1
- package/dist/api/index.d.ts +1 -1
- package/dist/client/Ablo.d.ts +9 -51
- package/dist/client/Ablo.js +2 -104
- package/dist/client/ApiClient.d.ts +3 -115
- package/dist/client/ApiClient.js +0 -232
- package/dist/client/auth.js +32 -2
- package/dist/client/httpClient.d.ts +5 -6
- package/dist/client/httpClient.js +2 -3
- package/dist/client/index.d.ts +1 -1
- package/dist/errorCodes.js +3 -3
- package/dist/index.js +1 -1
- package/dist/interfaces/index.d.ts +4 -4
- package/dist/mutators/UndoManager.d.ts +100 -11
- package/dist/mutators/UndoManager.js +282 -13
- package/dist/react/AbloProvider.d.ts +18 -8
- package/dist/react/context.d.ts +31 -0
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +1 -1
- package/dist/react/useUndoScope.js +7 -0
- package/dist/schema/ddl.d.ts +8 -0
- package/dist/schema/ddl.js +10 -0
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/index.js +1 -1
- package/dist/server/commit.d.ts +4 -5
- package/dist/source/adapter.d.ts +18 -12
- package/dist/source/adapter.js +8 -7
- package/dist/source/adapters/drizzle.d.ts +15 -6
- package/dist/source/adapters/drizzle.js +87 -49
- package/dist/source/adapters/memory.d.ts +1 -1
- package/dist/source/adapters/memory.js +2 -2
- package/dist/source/adapters/prisma.d.ts +3 -3
- package/dist/source/adapters/prisma.js +6 -29
- package/dist/source/conformance.d.ts +1 -1
- package/dist/source/conformance.js +2 -2
- package/dist/source/contract.d.ts +3 -2
- package/dist/source/contract.js +3 -2
- package/dist/source/index.d.ts +1 -0
- package/dist/source/index.js +3 -2
- package/dist/source/migrations.d.ts +14 -0
- package/dist/source/migrations.js +39 -0
- package/dist/types/streams.d.ts +2 -1
- package/dist/wire/frames.d.ts +6 -8
- package/docs/api.md +1 -1
- package/docs/cli.md +18 -5
- package/docs/data-sources.md +68 -83
- package/docs/examples/ai-sdk-tool.md +11 -5
- package/docs/examples/existing-python-backend.md +26 -4
- package/docs/examples/nextjs.md +3 -2
- package/docs/examples/scoped-agent.md +38 -11
- package/docs/identity.md +86 -59
- package/docs/index.md +1 -1
- package/docs/integration-guide.md +85 -54
- package/docs/react.md +39 -28
- package/llms.txt +18 -11
- package/package.json +2 -2
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* IndexedDB, no WebSocket. It maps the public Model / Claim / Commit
|
|
6
6
|
* nouns directly to HTTP routes on sync-server.
|
|
7
7
|
*/
|
|
8
|
-
import type { AbloOptions,
|
|
8
|
+
import type { AbloOptions, CommitResource, IntentCreateOptions, IntentHandle, IntentWaitOptions, ModelClient, ModelClaim, ModelTarget } from './Ablo.js';
|
|
9
9
|
import type { Duration } from '../utils/duration.js';
|
|
10
10
|
export type AbloApiClientOptions = Omit<AbloOptions, 'schema'> & {
|
|
11
11
|
readonly schema?: null | undefined;
|
|
@@ -115,133 +115,21 @@ export interface CapabilityResource {
|
|
|
115
115
|
*/
|
|
116
116
|
mint(options: CapabilityCreateOptions): Promise<Capability>;
|
|
117
117
|
}
|
|
118
|
-
export interface TaskCreateOptions {
|
|
119
|
-
readonly prompt: string;
|
|
120
|
-
readonly parentTaskId?: string;
|
|
121
|
-
readonly surface?: string;
|
|
122
|
-
readonly metadata?: Record<string, unknown>;
|
|
123
|
-
}
|
|
124
|
-
export interface TaskCloseOptions {
|
|
125
|
-
readonly costInputTokens?: number;
|
|
126
|
-
readonly costOutputTokens?: number;
|
|
127
|
-
readonly costComputeMs?: number;
|
|
128
|
-
}
|
|
129
|
-
export interface Task {
|
|
130
|
-
readonly id: string;
|
|
131
|
-
readonly turnId: string;
|
|
132
|
-
readonly promptHash?: string;
|
|
133
|
-
readonly openedAt?: string;
|
|
134
|
-
close(stats?: TaskCloseOptions): Promise<TaskCloseResult>;
|
|
135
|
-
}
|
|
136
|
-
export interface TaskCloseResult {
|
|
137
|
-
readonly id: string;
|
|
138
|
-
readonly turnId: string;
|
|
139
|
-
readonly closed: boolean;
|
|
140
|
-
readonly alreadyClosed?: boolean;
|
|
141
|
-
readonly endedAt?: string;
|
|
142
|
-
}
|
|
143
|
-
export interface TaskResource {
|
|
144
|
-
create(options: TaskCreateOptions): Promise<Task>;
|
|
145
|
-
close(id: string, stats?: TaskCloseOptions): Promise<TaskCloseResult>;
|
|
146
|
-
/**
|
|
147
|
-
* Alias for `create`. Kept for the agent-run vocabulary; `create` is
|
|
148
|
-
* the canonical SDK verb.
|
|
149
|
-
*/
|
|
150
|
-
open(options: TaskCreateOptions): Promise<Task>;
|
|
151
|
-
}
|
|
152
|
-
export interface AgentOptions {
|
|
153
|
-
readonly can: readonly string[];
|
|
154
|
-
readonly syncGroups?: readonly string[];
|
|
155
|
-
readonly label?: string;
|
|
156
|
-
readonly userMeta?: Record<string, unknown>;
|
|
157
|
-
/**
|
|
158
|
-
* Internal lease for the run capability. Most callers should omit it.
|
|
159
|
-
* The SDK revokes the capability when `run` finishes; the lease exists
|
|
160
|
-
* to clean up crashed or abandoned runs.
|
|
161
|
-
*/
|
|
162
|
-
readonly lease?: Duration;
|
|
163
|
-
readonly leaseSeconds?: number;
|
|
164
|
-
}
|
|
165
|
-
export interface AgentRunOptions extends TaskCreateOptions {
|
|
166
|
-
readonly signal?: AbortSignal;
|
|
167
|
-
readonly costInputTokens?: number;
|
|
168
|
-
readonly costOutputTokens?: number;
|
|
169
|
-
readonly costComputeMs?: number;
|
|
170
|
-
}
|
|
171
|
-
export type AgentRunStatus = 'done' | 'failed' | 'cancelled';
|
|
172
|
-
export interface AgentRunDone<T> {
|
|
173
|
-
readonly status: 'done';
|
|
174
|
-
readonly task: Task;
|
|
175
|
-
readonly value: T;
|
|
176
|
-
}
|
|
177
|
-
export interface AgentRunFailed {
|
|
178
|
-
readonly status: 'failed';
|
|
179
|
-
readonly task?: Task;
|
|
180
|
-
readonly error: unknown;
|
|
181
|
-
}
|
|
182
|
-
export interface AgentRunCancelled {
|
|
183
|
-
readonly status: 'cancelled';
|
|
184
|
-
readonly task?: Task;
|
|
185
|
-
readonly error?: unknown;
|
|
186
|
-
}
|
|
187
|
-
export type AgentRunResult<T> = AgentRunDone<T> | AgentRunFailed | AgentRunCancelled;
|
|
188
|
-
export interface AgentIntentOptions {
|
|
189
|
-
readonly action: string;
|
|
190
|
-
readonly field?: string;
|
|
191
|
-
readonly ttl?: Duration;
|
|
192
|
-
readonly target?: Partial<ModelTarget>;
|
|
193
|
-
}
|
|
194
|
-
export type AgentIntentInput = string | AgentIntentOptions;
|
|
195
|
-
export interface AgentModelReadOptions extends ModelReadOptions {
|
|
196
|
-
}
|
|
197
|
-
export interface AgentModelMutationOptions extends Omit<ModelMutationOptions, 'intent'> {
|
|
198
|
-
readonly intent?: AgentIntentInput | {
|
|
199
|
-
readonly id: string;
|
|
200
|
-
} | null;
|
|
201
|
-
}
|
|
202
|
-
export interface AgentModelClient<T = Record<string, unknown>> {
|
|
203
|
-
retrieve(params: AgentModelReadOptions & {
|
|
204
|
-
readonly id: string;
|
|
205
|
-
}): Promise<ModelRead<T>>;
|
|
206
|
-
create(params: AgentModelMutationOptions & {
|
|
207
|
-
readonly data: Record<string, unknown>;
|
|
208
|
-
readonly id?: string | null;
|
|
209
|
-
}): Promise<CommitReceipt>;
|
|
210
|
-
update(params: AgentModelMutationOptions & {
|
|
211
|
-
readonly id: string;
|
|
212
|
-
readonly data: Record<string, unknown>;
|
|
213
|
-
}): Promise<CommitReceipt>;
|
|
214
|
-
delete(params: AgentModelMutationOptions & {
|
|
215
|
-
readonly id: string;
|
|
216
|
-
}): Promise<CommitReceipt>;
|
|
217
|
-
}
|
|
218
|
-
export interface AgentRunContext {
|
|
219
|
-
readonly task: Task;
|
|
220
|
-
readonly ablo: AbloApi;
|
|
221
|
-
model<T = Record<string, unknown>>(name: string): AgentModelClient<T>;
|
|
222
|
-
}
|
|
223
|
-
export interface Agent {
|
|
224
|
-
readonly id: string;
|
|
225
|
-
run<T>(options: AgentRunOptions, handler: (context: AgentRunContext) => Promise<T> | T): Promise<AgentRunResult<T>>;
|
|
226
|
-
}
|
|
227
118
|
export interface AbloApi {
|
|
228
119
|
ready(): Promise<void>;
|
|
229
120
|
waitForFlush(): Promise<void>;
|
|
230
121
|
dispose(): Promise<void>;
|
|
231
122
|
purge(): Promise<void>;
|
|
232
123
|
readonly capabilities: CapabilityResource;
|
|
233
|
-
readonly tasks: TaskResource;
|
|
234
124
|
readonly intents: AbloApiIntents;
|
|
235
125
|
readonly commits: CommitResource;
|
|
236
|
-
agent(id: string, options: AgentOptions): Agent;
|
|
237
126
|
model<T = Record<string, unknown>>(name: string): ModelClient<T>;
|
|
238
|
-
beginTurn(options: TaskCreateOptions): Promise<Turn>;
|
|
239
127
|
/**
|
|
240
128
|
* Resolve the active bearer credential this client authenticates with — the
|
|
241
129
|
* same token its own requests carry in `Authorization`. Returns `null` when
|
|
242
130
|
* no credential is configured. Async because the API key may be supplied as
|
|
243
|
-
* an async setter. Use it to authenticate side-band
|
|
244
|
-
*
|
|
131
|
+
* an async setter. Use it to authenticate a side-band request to the same
|
|
132
|
+
* server with the credential this client already holds — no re-mint.
|
|
245
133
|
*/
|
|
246
134
|
getAuthToken(): Promise<string | null>;
|
|
247
135
|
}
|
package/dist/client/ApiClient.js
CHANGED
|
@@ -9,7 +9,6 @@ import { AbloClaimedError, AbloAuthenticationError, AbloConnectionError, AbloVal
|
|
|
9
9
|
import { assertBrowserSafety, readProcessEnv, resolveApiKey, resolveApiKeyValue, resolveAuthToken, resolveBaseURL, resolveBootstrapBaseUrl, } from './auth.js';
|
|
10
10
|
import { toSeconds } from '../utils/duration.js';
|
|
11
11
|
const DEFAULT_AGENT_LEASE = '10m';
|
|
12
|
-
const DEFAULT_INTENT_LEASE = '2m';
|
|
13
12
|
export function createProtocolClient(options) {
|
|
14
13
|
const env = readProcessEnv();
|
|
15
14
|
const authInput = { options, env };
|
|
@@ -100,144 +99,6 @@ export function createProtocolClient(options) {
|
|
|
100
99
|
schema: null,
|
|
101
100
|
});
|
|
102
101
|
}
|
|
103
|
-
function createAgent(id, agentOptions) {
|
|
104
|
-
return {
|
|
105
|
-
id,
|
|
106
|
-
async run(runOptions, handler) {
|
|
107
|
-
if (runOptions.signal?.aborted) {
|
|
108
|
-
return { status: 'cancelled' };
|
|
109
|
-
}
|
|
110
|
-
let capability = null;
|
|
111
|
-
let task = null;
|
|
112
|
-
try {
|
|
113
|
-
const leaseOptions = agentOptions.leaseSeconds !== undefined
|
|
114
|
-
? { leaseSeconds: agentOptions.leaseSeconds }
|
|
115
|
-
: { lease: agentOptions.lease ?? DEFAULT_AGENT_LEASE };
|
|
116
|
-
capability = await capabilities.create({
|
|
117
|
-
participantKind: 'agent',
|
|
118
|
-
participantId: id,
|
|
119
|
-
syncGroups: agentOptions.syncGroups ?? ['default'],
|
|
120
|
-
operations: agentOptions.can,
|
|
121
|
-
label: agentOptions.label ?? id,
|
|
122
|
-
userMeta: agentOptions.userMeta,
|
|
123
|
-
...leaseOptions,
|
|
124
|
-
});
|
|
125
|
-
const agentClient = capability.client();
|
|
126
|
-
task = await agentClient.tasks.create({
|
|
127
|
-
prompt: runOptions.prompt,
|
|
128
|
-
parentTaskId: runOptions.parentTaskId,
|
|
129
|
-
surface: runOptions.surface ?? 'agent',
|
|
130
|
-
metadata: runOptions.metadata,
|
|
131
|
-
});
|
|
132
|
-
const context = createAgentRunContext(agentClient, task);
|
|
133
|
-
const value = await handler(context);
|
|
134
|
-
await task.close({
|
|
135
|
-
costInputTokens: runOptions.costInputTokens,
|
|
136
|
-
costOutputTokens: runOptions.costOutputTokens,
|
|
137
|
-
costComputeMs: runOptions.costComputeMs,
|
|
138
|
-
});
|
|
139
|
-
return { status: 'done', task, value };
|
|
140
|
-
}
|
|
141
|
-
catch (error) {
|
|
142
|
-
if (task) {
|
|
143
|
-
await task.close({
|
|
144
|
-
costInputTokens: runOptions.costInputTokens,
|
|
145
|
-
costOutputTokens: runOptions.costOutputTokens,
|
|
146
|
-
costComputeMs: runOptions.costComputeMs,
|
|
147
|
-
}).catch(() => { });
|
|
148
|
-
}
|
|
149
|
-
if (isAbortError(error) || runOptions.signal?.aborted) {
|
|
150
|
-
return { status: 'cancelled', task: task ?? undefined, error };
|
|
151
|
-
}
|
|
152
|
-
return { status: 'failed', task: task ?? undefined, error };
|
|
153
|
-
}
|
|
154
|
-
finally {
|
|
155
|
-
if (capability) {
|
|
156
|
-
await capabilities.revoke(capability.id).catch(() => { });
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
},
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
function createAgentRunContext(agentClient, task) {
|
|
163
|
-
return {
|
|
164
|
-
task,
|
|
165
|
-
ablo: agentClient,
|
|
166
|
-
model(name) {
|
|
167
|
-
return createAgentModelClient(agentClient, name);
|
|
168
|
-
},
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
function createAgentModelClient(agentClient, name) {
|
|
172
|
-
const base = agentClient.model(name);
|
|
173
|
-
return {
|
|
174
|
-
retrieve(params) {
|
|
175
|
-
// Reads are never blocked by a claim (coordination.md): a claim
|
|
176
|
-
// serializes WRITERS, not readers. So — unlike the create/update/
|
|
177
|
-
// delete paths below — retrieve does NOT apply the agent claimed
|
|
178
|
-
// default; options pass through and the read path's `'return'`
|
|
179
|
-
// default keeps a claimed row readable. A caller can still opt into
|
|
180
|
-
// gating with an explicit `ifClaimed` (developer's choice).
|
|
181
|
-
return base.retrieve(params);
|
|
182
|
-
},
|
|
183
|
-
create(params) {
|
|
184
|
-
const id = params.id ?? createModelId();
|
|
185
|
-
return withAgentIntent(agentClient, name, id, params, (commitIntent) => base.create({
|
|
186
|
-
...stripAgentRuntimeOptions(params),
|
|
187
|
-
id,
|
|
188
|
-
data: params.data,
|
|
189
|
-
intent: commitIntent,
|
|
190
|
-
}));
|
|
191
|
-
},
|
|
192
|
-
update(params) {
|
|
193
|
-
return withAgentIntent(agentClient, name, params.id, params, (commitIntent) => base.update({
|
|
194
|
-
...stripAgentRuntimeOptions(params),
|
|
195
|
-
id: params.id,
|
|
196
|
-
data: params.data,
|
|
197
|
-
intent: commitIntent,
|
|
198
|
-
}));
|
|
199
|
-
},
|
|
200
|
-
delete(params) {
|
|
201
|
-
return withAgentIntent(agentClient, name, params.id, params, (commitIntent) => base.delete({
|
|
202
|
-
...stripAgentRuntimeOptions(params),
|
|
203
|
-
id: params.id,
|
|
204
|
-
intent: commitIntent,
|
|
205
|
-
}));
|
|
206
|
-
},
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
async function withAgentIntent(agentClient, modelName, id, mutationOptions, commit) {
|
|
210
|
-
const intentInput = mutationOptions?.intent;
|
|
211
|
-
const targetOverride = intentInput != null && typeof intentInput === 'object' && !isIntentHandleRef(intentInput)
|
|
212
|
-
? intentInput.target ?? {}
|
|
213
|
-
: {};
|
|
214
|
-
const target = {
|
|
215
|
-
...targetOverride,
|
|
216
|
-
model: targetOverride.model ?? modelName,
|
|
217
|
-
id: targetOverride.id ?? id,
|
|
218
|
-
...(intentInput != null && typeof intentInput === 'object' && !isIntentHandleRef(intentInput) && intentInput.field
|
|
219
|
-
? { field: intentInput.field }
|
|
220
|
-
: {}),
|
|
221
|
-
};
|
|
222
|
-
await applyClaimedPolicy(target, withAgentClaimedDefault(mutationOptions), 'wait');
|
|
223
|
-
if (intentInput == null || isIntentHandleRef(intentInput)) {
|
|
224
|
-
return commit(intentInput);
|
|
225
|
-
}
|
|
226
|
-
const action = typeof intentInput === 'string' ? intentInput : intentInput.action;
|
|
227
|
-
const intent = await agentClient.intents.create({
|
|
228
|
-
target,
|
|
229
|
-
action,
|
|
230
|
-
ttl: typeof intentInput === 'object'
|
|
231
|
-
? intentInput.ttl ?? DEFAULT_INTENT_LEASE
|
|
232
|
-
: DEFAULT_INTENT_LEASE,
|
|
233
|
-
});
|
|
234
|
-
try {
|
|
235
|
-
return await commit(intent);
|
|
236
|
-
}
|
|
237
|
-
finally {
|
|
238
|
-
await intent.release().catch(() => { });
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
102
|
function normalizeCommitOperation(op, defaults) {
|
|
242
103
|
const model = op.model ?? op.target?.model;
|
|
243
104
|
if (!model) {
|
|
@@ -476,51 +337,6 @@ export function createProtocolClient(options) {
|
|
|
476
337
|
return capabilities.create(options);
|
|
477
338
|
},
|
|
478
339
|
};
|
|
479
|
-
const tasks = {
|
|
480
|
-
async create(taskOptions) {
|
|
481
|
-
const body = await requestJson('/v1/tasks', {
|
|
482
|
-
method: 'POST',
|
|
483
|
-
body: JSON.stringify({
|
|
484
|
-
prompt: taskOptions.prompt,
|
|
485
|
-
parentTaskId: taskOptions.parentTaskId,
|
|
486
|
-
surface: taskOptions.surface,
|
|
487
|
-
metadata: taskOptions.metadata,
|
|
488
|
-
}),
|
|
489
|
-
});
|
|
490
|
-
const id = body.id ?? body.taskId ?? body.turnId;
|
|
491
|
-
if (!id) {
|
|
492
|
-
throw new AbloValidationError('Task create response did not include an id.', { code: 'task_id_missing' });
|
|
493
|
-
}
|
|
494
|
-
return {
|
|
495
|
-
id,
|
|
496
|
-
turnId: id,
|
|
497
|
-
promptHash: body.promptHash,
|
|
498
|
-
openedAt: body.openedAt,
|
|
499
|
-
close: (stats) => tasks.close(id, stats),
|
|
500
|
-
};
|
|
501
|
-
},
|
|
502
|
-
async close(id, stats) {
|
|
503
|
-
const body = await requestJson(`/v1/tasks/${encodeURIComponent(id)}/close`, {
|
|
504
|
-
method: 'POST',
|
|
505
|
-
body: JSON.stringify({
|
|
506
|
-
costInputTokens: stats?.costInputTokens ?? 0,
|
|
507
|
-
costOutputTokens: stats?.costOutputTokens ?? 0,
|
|
508
|
-
costComputeMs: stats?.costComputeMs ?? 0,
|
|
509
|
-
}),
|
|
510
|
-
});
|
|
511
|
-
const closedId = body.id ?? body.taskId ?? body.turnId ?? id;
|
|
512
|
-
return {
|
|
513
|
-
id: closedId,
|
|
514
|
-
turnId: closedId,
|
|
515
|
-
closed: body.closed ?? body.alreadyClosed ?? true,
|
|
516
|
-
alreadyClosed: body.alreadyClosed,
|
|
517
|
-
endedAt: body.endedAt,
|
|
518
|
-
};
|
|
519
|
-
},
|
|
520
|
-
open(options) {
|
|
521
|
-
return tasks.create(options);
|
|
522
|
-
},
|
|
523
|
-
};
|
|
524
340
|
const intents = {
|
|
525
341
|
async create(intentOptions) {
|
|
526
342
|
const intentId = createIntentId();
|
|
@@ -780,35 +596,14 @@ export function createProtocolClient(options) {
|
|
|
780
596
|
async dispose() { },
|
|
781
597
|
async purge() { },
|
|
782
598
|
capabilities,
|
|
783
|
-
tasks,
|
|
784
599
|
intents,
|
|
785
600
|
commits,
|
|
786
601
|
model,
|
|
787
|
-
agent: createAgent,
|
|
788
602
|
async getAuthToken() {
|
|
789
603
|
// Mirror `authHeaders()`: a configured API key wins, else the
|
|
790
604
|
// construction-time auth token. Resolve the (possibly async) key setter.
|
|
791
605
|
return (await resolveApiKeyValue(configuredApiKey)) ?? configuredAuthToken ?? null;
|
|
792
606
|
},
|
|
793
|
-
async beginTurn(turnOptions) {
|
|
794
|
-
const task = await tasks.create(turnOptions);
|
|
795
|
-
let closed = false;
|
|
796
|
-
const close = async (stats) => {
|
|
797
|
-
if (closed)
|
|
798
|
-
return;
|
|
799
|
-
closed = true;
|
|
800
|
-
await task.close(stats);
|
|
801
|
-
};
|
|
802
|
-
const dispose = () => {
|
|
803
|
-
closed = true;
|
|
804
|
-
};
|
|
805
|
-
return {
|
|
806
|
-
turnId: task.id,
|
|
807
|
-
close,
|
|
808
|
-
dispose,
|
|
809
|
-
[Symbol.asyncDispose]: close,
|
|
810
|
-
};
|
|
811
|
-
},
|
|
812
607
|
};
|
|
813
608
|
}
|
|
814
609
|
function normalizeIntentId(intent) {
|
|
@@ -816,33 +611,6 @@ function normalizeIntentId(intent) {
|
|
|
816
611
|
return intent;
|
|
817
612
|
return intent?.id;
|
|
818
613
|
}
|
|
819
|
-
function withAgentClaimedDefault(options) {
|
|
820
|
-
return {
|
|
821
|
-
ifClaimed: 'fail',
|
|
822
|
-
...(options ?? {}),
|
|
823
|
-
};
|
|
824
|
-
}
|
|
825
|
-
function stripAgentRuntimeOptions(options) {
|
|
826
|
-
if (!options)
|
|
827
|
-
return undefined;
|
|
828
|
-
const { intent: _intent, ifClaimed: _ifClaimed, claimedTimeout: _claimedTimeout, claimedPollInterval: _claimedPollInterval, maxQueueDepth: _maxQueueDepth, ...rest } = options;
|
|
829
|
-
return rest;
|
|
830
|
-
}
|
|
831
|
-
function isIntentHandleRef(input) {
|
|
832
|
-
return (typeof input === 'object' &&
|
|
833
|
-
input !== null &&
|
|
834
|
-
'id' in input &&
|
|
835
|
-
typeof input.id === 'string' &&
|
|
836
|
-
!('action' in input));
|
|
837
|
-
}
|
|
838
|
-
function isAbortError(error) {
|
|
839
|
-
return (typeof DOMException !== 'undefined' &&
|
|
840
|
-
error instanceof DOMException &&
|
|
841
|
-
error.name === 'AbortError') || (typeof error === 'object' &&
|
|
842
|
-
error !== null &&
|
|
843
|
-
'name' in error &&
|
|
844
|
-
error.name === 'AbortError');
|
|
845
|
-
}
|
|
846
614
|
function parseBody(bodyText) {
|
|
847
615
|
if (bodyText.length === 0)
|
|
848
616
|
return null;
|
package/dist/client/auth.js
CHANGED
|
@@ -146,8 +146,38 @@ export function resolveBootstrapBaseUrl(input) {
|
|
|
146
146
|
// legitimately arrive as `wss://…` — normalize it here rather than
|
|
147
147
|
// faceplanting at fetch time. The derive branch below already does this;
|
|
148
148
|
// the override branch silently skipped it.
|
|
149
|
-
return normalizeAbloHostedBaseUrl(input.bootstrapBaseUrl).replace(/^ws/, 'http');
|
|
149
|
+
return ensureApiSuffix(normalizeAbloHostedBaseUrl(input.bootstrapBaseUrl).replace(/^ws/, 'http'));
|
|
150
150
|
}
|
|
151
151
|
const url = normalizeAbloHostedBaseUrl(input.url);
|
|
152
|
-
return
|
|
152
|
+
return ensureApiSuffix(url.replace(/^ws/, 'http'));
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Guarantee the HTTP base ends in the `/api` route segment the sync-server
|
|
156
|
+
* mounts every endpoint under (`apps/sync-server/src/index.ts` — `app.route('/api', …)`).
|
|
157
|
+
*
|
|
158
|
+
* The derive branch always appended `/api`; the override branch did NOT,
|
|
159
|
+
* trusting the caller (apps/web passes `${baseUrl}/api`). But a hosted
|
|
160
|
+
* customer setting a custom `baseURL`/`bootstrapBaseUrl` (their own subdomain,
|
|
161
|
+
* staging, etc.) without the suffix sent every credential exchange to
|
|
162
|
+
* `…/auth/capability` instead of `…/api/auth/capability` → a 404 surfaced as
|
|
163
|
+
* `exchange_failed`. Since the SDK hardcodes routes relative to this base and
|
|
164
|
+
* there is no valid Ablo deployment that serves them off the root, normalizing
|
|
165
|
+
* to a single trailing `/api` here is always correct — and idempotent for
|
|
166
|
+
* callers who already include it.
|
|
167
|
+
*/
|
|
168
|
+
function ensureApiSuffix(httpBase) {
|
|
169
|
+
const trimmed = httpBase.replace(/\/+$/, '');
|
|
170
|
+
try {
|
|
171
|
+
const u = new URL(trimmed);
|
|
172
|
+
const segments = u.pathname.split('/').filter(Boolean);
|
|
173
|
+
if (segments[segments.length - 1] === 'api')
|
|
174
|
+
return trimmed;
|
|
175
|
+
u.pathname = `${u.pathname.replace(/\/+$/, '')}/api`;
|
|
176
|
+
return u.toString().replace(/\/+$/, '');
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// Should be unreachable post-`normalizeAbloHostedBaseUrl` (which yields an
|
|
180
|
+
// absolute URL), but fall back to a string check rather than throwing.
|
|
181
|
+
return /\/api$/.test(trimmed) ? trimmed : `${trimmed}/api`;
|
|
182
|
+
}
|
|
153
183
|
}
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
* wraps it in a typed proxy facade so server code gets the SAME `client.<model>`
|
|
23
23
|
* surface as the browser client — typed proxies, stateless transport.
|
|
24
24
|
*/
|
|
25
|
-
import { type AbloApiClientOptions
|
|
26
|
-
import type { CommitReceipt, CommitResource, HttpClaimApi, ModelRead, ModelReadOptions
|
|
25
|
+
import { type AbloApiClientOptions } from './ApiClient.js';
|
|
26
|
+
import type { CommitReceipt, CommitResource, HttpClaimApi, ModelRead, ModelReadOptions } from './Ablo.js';
|
|
27
27
|
import type { ModelCreateParams, ModelDeleteParams, ModelLoadOptions, ModelRetrieveParams, ModelUpdateParams } from './createModelProxy.js';
|
|
28
28
|
import type { Schema, SchemaRecord, InferModel, InferCreate } from '../schema/schema.js';
|
|
29
29
|
export interface AbloHttpClientOptions<S extends SchemaRecord> extends Omit<AbloApiClientOptions, 'schema'> {
|
|
@@ -47,7 +47,7 @@ export interface HttpModelClient<T, C = T> {
|
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
49
|
* The honest type of the stateless HTTP client: typed model proxies (the
|
|
50
|
-
* request/response subset) + `commits` + `
|
|
50
|
+
* request/response subset) + `commits` + `dispose`. Reaching for a
|
|
51
51
|
* stateful-only capability (`get`/`getAll`/`getCount`, `onChange`,
|
|
52
52
|
* `claim.state`/`queue`/`reorder`) is a COMPILE error here, not a latent runtime
|
|
53
53
|
* `undefined` — the type matches what the transport can actually do.
|
|
@@ -56,7 +56,6 @@ export type AbloHttpClient<S extends SchemaRecord> = {
|
|
|
56
56
|
readonly [K in keyof S & string]: HttpModelClient<InferModel<Schema<S>, K>, InferCreate<Schema<S>, K>>;
|
|
57
57
|
} & {
|
|
58
58
|
readonly commits: CommitResource;
|
|
59
|
-
beginTurn(options: TaskCreateOptions): Promise<Turn>;
|
|
60
59
|
dispose(): Promise<void>;
|
|
61
60
|
/** Resolve the bearer credential this client authenticates with (see `AbloApi.getAuthToken`). */
|
|
62
61
|
getAuthToken(): Promise<string | null>;
|
|
@@ -65,7 +64,7 @@ export type AbloHttpClient<S extends SchemaRecord> = {
|
|
|
65
64
|
};
|
|
66
65
|
/**
|
|
67
66
|
* Stateless, typed HTTP client. Each `client.<model>` resolves to the protocol
|
|
68
|
-
* client's `model(name)`; `commits`, `
|
|
69
|
-
*
|
|
67
|
+
* client's `model(name)`; `commits`, `dispose`, etc. pass through. No socket is
|
|
68
|
+
* ever opened; identity is the Bearer credential.
|
|
70
69
|
*/
|
|
71
70
|
export declare function createAbloHttpClient<S extends SchemaRecord>(options: AbloHttpClientOptions<S>): AbloHttpClient<S>;
|
|
@@ -36,14 +36,13 @@ const PROTOCOL_MEMBERS = new Set([
|
|
|
36
36
|
'dispose',
|
|
37
37
|
'purge',
|
|
38
38
|
'commits',
|
|
39
|
-
'beginTurn',
|
|
40
39
|
'model',
|
|
41
40
|
'getAuthToken',
|
|
42
41
|
]);
|
|
43
42
|
/**
|
|
44
43
|
* Stateless, typed HTTP client. Each `client.<model>` resolves to the protocol
|
|
45
|
-
* client's `model(name)`; `commits`, `
|
|
46
|
-
*
|
|
44
|
+
* client's `model(name)`; `commits`, `dispose`, etc. pass through. No socket is
|
|
45
|
+
* ever opened; identity is the Bearer credential.
|
|
47
46
|
*/
|
|
48
47
|
export function createAbloHttpClient(options) {
|
|
49
48
|
// The schema is type-level only; the protocol client is schema-agnostic.
|
package/dist/client/index.d.ts
CHANGED
|
@@ -32,5 +32,5 @@
|
|
|
32
32
|
export { Ablo, computeFKDepthPriority, type AbloOptions, type InternalAbloOptions, type ClaimedOptions, type IfClaimedPolicy, type IntentWaitOptions, type ModelCountOptions, type ModelListOptions, type ModelListScope, type ModelLoadOptions, type ModelOperations, type ModelReadOptions, } from './Ablo.js';
|
|
33
33
|
export { ABLO_DEFAULT_BASE_URL, ABLO_HOSTED_API_DOMAIN, ABLO_HOSTED_HTTP_BASE_URL, normalizeAbloHostedBaseUrl, } from './auth.js';
|
|
34
34
|
export type { AbloPersistence } from './persistence.js';
|
|
35
|
-
export type { AbloApi, AbloApiClientOptions, AbloApiIntents,
|
|
35
|
+
export type { AbloApi, AbloApiClientOptions, AbloApiIntents, Capability, CapabilityCreateOptions, CapabilityParticipantKind, CapabilityRecord, CapabilityResource, CapabilityRevocation, CapabilityScope, } from './ApiClient.js';
|
|
36
36
|
export type { EngineParticipant, JoinedParticipant, ParticipantJoinOptions, ParticipantManager, ParticipantScope, ParticipantStatus, ScopedIntents, ScopedPresence, } from '../sync/participants.js';
|
package/dist/errorCodes.js
CHANGED
|
@@ -176,8 +176,8 @@ export const ERROR_CODES = {
|
|
|
176
176
|
check_violation: wire('validation', 400, false, 'A value violates a database check constraint.'),
|
|
177
177
|
constraint_violation: wire('validation', 400, false, 'A database integrity constraint was violated.'),
|
|
178
178
|
// ── tenant / unknown model (400) ───────────────────────────────────
|
|
179
|
-
server_execute_unknown_model: wire('tenant', 400, false, 'The server
|
|
180
|
-
mutate_create_unknown_model: wire('tenant', 400, false, '
|
|
179
|
+
server_execute_unknown_model: wire('tenant', 400, false, 'Wrote to a model the server does not know. The server keeps its own copy of the schema — run `ablo push` (or keep `ablo dev` running) to upload `ablo/schema.ts` before writing to new or changed models.'),
|
|
180
|
+
mutate_create_unknown_model: wire('tenant', 400, false, 'Created a model the server does not know. Run `ablo push` (or keep `ablo dev` running) to upload `ablo/schema.ts` first — the server keeps its own copy of the schema.'),
|
|
181
181
|
tenant_model_columns_unknown: wire('tenant', 400, false, "The tenant model's columns could not be resolved."),
|
|
182
182
|
tenant_model_missing_organization_id: wire('tenant', 400, false, 'The tenant model is missing the organization_id column required for isolation.'),
|
|
183
183
|
// ── schema migration / declaration (validation) ────────────────────
|
|
@@ -286,7 +286,7 @@ export const ERROR_CODES = {
|
|
|
286
286
|
provisioner_unavailable: wire('server', 503, false, 'No database provisioner is configured.'),
|
|
287
287
|
invalid_model: wire('validation', 400, false, 'The request named an invalid model.'),
|
|
288
288
|
invalid_id: wire('validation', 400, false, 'The request carried an invalid id.'),
|
|
289
|
-
unknown_model: wire('tenant', 400, false, '
|
|
289
|
+
unknown_model: wire('tenant', 400, false, 'Named a model the server does not know. Run `ablo push` (or keep `ablo dev` running) to upload `ablo/schema.ts` — the server keeps its own copy of the schema.'),
|
|
290
290
|
model_not_tenant_scoped: wire('tenant', 400, false, 'The model is not tenant-scoped and cannot be queried this way.'),
|
|
291
291
|
schema_table_invalid: wire('schema', 500, false, "The model's table identifier is invalid."),
|
|
292
292
|
schema_scope_invalid: wire('schema', 500, false, "The model's scope predicate could not be built."),
|
package/dist/index.js
CHANGED
|
@@ -61,7 +61,7 @@ export { ABLO_DEFAULT_BASE_URL, ABLO_HOSTED_API_DOMAIN, ABLO_HOSTED_HTTP_BASE_UR
|
|
|
61
61
|
// Participant types live under `Ablo.Participant.*` —
|
|
62
62
|
// `Ablo.Participant.Joined`, `Ablo.Participant.Manager`,
|
|
63
63
|
// `Ablo.Participant.JoinOptions`, etc. Same dot-access shape as
|
|
64
|
-
// `Ablo.Peer`, `Ablo.Claim
|
|
64
|
+
// `Ablo.Peer`, `Ablo.Claim`. No flat re-exports.
|
|
65
65
|
// Advanced — most apps never import this. Principal constructors for
|
|
66
66
|
// delegated agent paths (`Ablo({ kind: 'agent', as: session({...}) })`).
|
|
67
67
|
// The default `Ablo({ schema, apiKey })` resolves identity from the key;
|
|
@@ -164,10 +164,10 @@ export interface MutationOptions {
|
|
|
164
164
|
readonly id: string;
|
|
165
165
|
} | null;
|
|
166
166
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
167
|
+
* Dormant agent-task lineage field, forwarded as the wire-level
|
|
168
|
+
* `causedByTaskId`. Turns/tasks were removed from the SDK; nothing
|
|
169
|
+
* populates this anymore (write attribution rides on the claim/intent
|
|
170
|
+
* id). Kept optional for wire-compat; always `null` from the client.
|
|
171
171
|
*/
|
|
172
172
|
causedByTaskId?: string | null;
|
|
173
173
|
}
|