@ziggs-ai/api-client 0.37.10 → 0.37.12
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/capabilities/context.js +22 -29
- package/dist/capabilities/mcpBriefs.js +1 -1
- package/dist/handles.d.ts +34 -0
- package/dist/handles.js +99 -0
- package/dist/http/ContextOpenClient.d.ts +2 -0
- package/dist/http/ContextOpenClient.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isContextAccessRequest } from '../shared/contextAccessRequest.js';
|
|
2
|
+
import { looksLikeHandle, parseRef } from '../handles.js';
|
|
2
3
|
import { presentResourceActions } from './resourceActions.js';
|
|
3
4
|
import { ContextReadClient, CONTEXT_READ_TYPES, CONTEXT_READ_VIA, parseVia, viaHint, } from '../http/ContextReadClient.js';
|
|
4
5
|
import { ContextOpenClient } from '../http/ContextOpenClient.js';
|
|
@@ -255,31 +256,24 @@ export const contextDelegateCapability = {
|
|
|
255
256
|
};
|
|
256
257
|
},
|
|
257
258
|
};
|
|
258
|
-
const
|
|
259
|
-
|
|
259
|
+
const OPEN_REF_PARAMS = {
|
|
260
|
+
ref: {
|
|
260
261
|
type: 'string',
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
chatId: {
|
|
264
|
-
type: 'string',
|
|
265
|
-
description: 'Chat id from a prior response. Pass exactly one id field.',
|
|
266
|
-
},
|
|
267
|
-
taskId: {
|
|
268
|
-
type: 'string',
|
|
269
|
-
description: 'Task id from a prior response. Pass exactly one id field.',
|
|
270
|
-
},
|
|
271
|
-
agreementId: {
|
|
272
|
-
type: 'string',
|
|
273
|
-
description: 'Agreement id from a prior response. Pass exactly one id field.',
|
|
262
|
+
required: true,
|
|
263
|
+
description: 'What to open: a handle such as T7K2QXD (T task, A agreement, C chat, F artifact) in any case, a Ziggs web URL holding one, or an id from a prior response.',
|
|
274
264
|
},
|
|
275
265
|
};
|
|
276
266
|
function openBodyFromArgs(args) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
267
|
+
// A mistyped handle is refused here, before any call: its check letter
|
|
268
|
+
// fails. Any other id goes to the server, which knows older shapes.
|
|
269
|
+
const raw = typeof args.ref === 'string' ? args.ref.trim() : '';
|
|
270
|
+
const invalid = (message) => Object.assign(new Error(message), { code: 'INVALID_INPUT' });
|
|
271
|
+
if (!raw)
|
|
272
|
+
throw invalid('ref is required: a handle such as T7K2QXD, a Ziggs URL, or an id from a prior response');
|
|
273
|
+
const ref = parseRef(raw);
|
|
274
|
+
if (!ref && looksLikeHandle(raw))
|
|
275
|
+
throw invalid(`${raw} is not a handle: its check letter does not match. Check it for a typo`);
|
|
276
|
+
const body = { ref: ref?.id ?? raw };
|
|
283
277
|
if (typeof args['cursor'] === 'string')
|
|
284
278
|
body.cursor = args['cursor'];
|
|
285
279
|
if (typeof args['after'] === 'string')
|
|
@@ -304,7 +298,7 @@ function presentOpenResult(result, env) {
|
|
|
304
298
|
const ask = row.grantRequest;
|
|
305
299
|
if (row.status !== 'active' || !isContextAccessRequest(ask) || !actingAgentId || ask.holderId !== actingAgentId || !ask.scope?.id || !['artifact', 'chat', 'agreement', 'task'].includes(ask.scope.kind ?? ''))
|
|
306
300
|
return [];
|
|
307
|
-
return [{ kind: 'resume', tool: env.surface === 'mcp' ? 'ziggs_open' : 'open', args: {
|
|
301
|
+
return [{ kind: 'resume', tool: env.surface === 'mcp' ? 'ziggs_open' : 'open', args: { ref: ask.scope.id }, effect: 'read_only', note: 'Approval recorded. Open the original scope; the server rechecks expiry and revocation.' }];
|
|
308
302
|
});
|
|
309
303
|
return {
|
|
310
304
|
...result,
|
|
@@ -353,16 +347,15 @@ export const openCapability = {
|
|
|
353
347
|
names: { sdk: 'open', mcp: 'ziggs_open' },
|
|
354
348
|
title: 'Open a returned resource',
|
|
355
349
|
descriptions: {
|
|
356
|
-
sdk: 'Read a returned resource: pass
|
|
350
|
+
sdk: 'Read a returned resource: pass its ref, a short handle such as T7K2QXD (the first letter is the kind), a Ziggs URL, or an id. The backend resolves access and returns scoped actions with required inputs and recovery calls. Opening executes none of those actions. Extraction pending/failed is not an empty document. To read a chat\'s messages under one agreement, use context_read with type=messages, via=chat:<id> and its agreement filter.',
|
|
357
351
|
},
|
|
358
352
|
annotation: 'read-only',
|
|
359
|
-
//
|
|
360
|
-
// (OPEN_ID_PARAMS). It used to carry context_read's filter wording, so an
|
|
353
|
+
// ref names the one record to open (OPEN_REF_PARAMS). It used to carry context_read's filter wording, so an
|
|
361
354
|
// agent passed it beside chatId as a filter and was refused by the
|
|
362
355
|
// exactly-one rule. Filtering a room's messages to one agreement is
|
|
363
356
|
// context_read's job, and the description says so.
|
|
364
357
|
params: {
|
|
365
|
-
...
|
|
358
|
+
...OPEN_REF_PARAMS,
|
|
366
359
|
cursor: { type: 'string', description: 'Opaque cursor from a prior open page' },
|
|
367
360
|
after: { type: 'string', description: 'ISO timestamp for a forward-delta' },
|
|
368
361
|
limit: { type: 'number', description: 'Page size' },
|
|
@@ -379,10 +372,10 @@ export const accessExplainCapability = {
|
|
|
379
372
|
names: { sdk: 'access_explain', mcp: 'ziggs_access_explain' },
|
|
380
373
|
title: 'Explain access to a resource',
|
|
381
374
|
descriptions: {
|
|
382
|
-
sdk: 'Read-only access explanation for
|
|
375
|
+
sdk: 'Read-only access explanation for a returned resource. Same ref as open. Returns scoped action states, required inputs and recovery calls. Does not return content and does not request or approve anything.',
|
|
383
376
|
},
|
|
384
377
|
annotation: 'read-only',
|
|
385
|
-
params:
|
|
378
|
+
params: OPEN_REF_PARAMS,
|
|
386
379
|
needsAgentId: true,
|
|
387
380
|
handler: async (args, env) => {
|
|
388
381
|
const creds = fullCreds(env);
|
|
@@ -508,7 +501,7 @@ export const accessRequestCapability = {
|
|
|
508
501
|
// A person scope opens nothing: the yes shows up in the chat list.
|
|
509
502
|
...(scopeKind === 'person'
|
|
510
503
|
? env.surface === 'mcp' ? { resumeAfterApproval: { tool: 'ziggs_chat_list', args: {} } } : {}
|
|
511
|
-
: { resumeAfterApproval: { tool: env.surface === 'mcp' ? 'ziggs_open' : 'open', args:
|
|
504
|
+
: { resumeAfterApproval: { tool: env.surface === 'mcp' ? 'ziggs_open' : 'open', args: { ref: result.scope?.id ?? scopeId } } }),
|
|
512
505
|
note: `${result.message ?? 'A request card is open.'} No access granted until they approve; then open the original resource using the same agent identity.`,
|
|
513
506
|
};
|
|
514
507
|
},
|
|
@@ -113,7 +113,7 @@ const BRIEFS = {
|
|
|
113
113
|
},
|
|
114
114
|
// Reading context
|
|
115
115
|
ziggs_open: {
|
|
116
|
-
does: 'Reads one
|
|
116
|
+
does: 'Reads one task, agreement, chat or artifact by ref: a handle like T7K2QXD, a Ziggs URL or an id. Returns the actions you may take. Hidden and unknown look the same. Messages under one agreement: ziggs_context_read.',
|
|
117
117
|
},
|
|
118
118
|
ziggs_access_explain: {
|
|
119
119
|
does: 'Explains what you may do with one artifact, chat, task or agreement, and the recovery calls; runs none of them.',
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Short handles: the id of a task (T…), agreement (A…), chat (C…) or
|
|
3
|
+
* artifact (F…). A kind letter, five to eight letters from 0-9 A-Z without
|
|
4
|
+
* I, L, O and U, and a check letter. Read in any case; written upper case.
|
|
5
|
+
*
|
|
6
|
+
* Mirrors the server's rule so a mistyped handle is refused here, before any
|
|
7
|
+
* call. The server stays the judge of what the handle opens.
|
|
8
|
+
*/
|
|
9
|
+
export declare const HANDLE_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
10
|
+
export type HandleKind = 'task' | 'agreement' | 'chat' | 'artifact';
|
|
11
|
+
/** The handle's kind and its upper-case form, or null when it is not one. */
|
|
12
|
+
export declare function parseHandle(raw: string): {
|
|
13
|
+
kind: HandleKind;
|
|
14
|
+
id: string;
|
|
15
|
+
} | null;
|
|
16
|
+
/** The shape of a handle, whether or not its check letter holds. */
|
|
17
|
+
export declare function looksLikeHandle(raw: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* What a `ref` names: a handle in any case, a Ziggs web URL that contains
|
|
20
|
+
* one, or an id of the older shape. Null when it names nothing.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseRef(raw: unknown): {
|
|
23
|
+
kind: HandleKind;
|
|
24
|
+
id: string;
|
|
25
|
+
} | null;
|
|
26
|
+
/**
|
|
27
|
+
* Every handle named in free text, in order and once each, so an agent that
|
|
28
|
+
* reads "look at T7K2QXD" can open it without anyone pasting a form. Words
|
|
29
|
+
* that merely look like one fail the check letter and are left out.
|
|
30
|
+
*/
|
|
31
|
+
export declare function findHandles(text: string): Array<{
|
|
32
|
+
kind: HandleKind;
|
|
33
|
+
id: string;
|
|
34
|
+
}>;
|
package/dist/handles.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Short handles: the id of a task (T…), agreement (A…), chat (C…) or
|
|
3
|
+
* artifact (F…). A kind letter, five to eight letters from 0-9 A-Z without
|
|
4
|
+
* I, L, O and U, and a check letter. Read in any case; written upper case.
|
|
5
|
+
*
|
|
6
|
+
* Mirrors the server's rule so a mistyped handle is refused here, before any
|
|
7
|
+
* call. The server stays the judge of what the handle opens.
|
|
8
|
+
*/
|
|
9
|
+
export const HANDLE_ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
|
10
|
+
const LETTER_KIND = { T: 'task', A: 'agreement', C: 'chat', F: 'artifact' };
|
|
11
|
+
const N = HANDLE_ALPHABET.length;
|
|
12
|
+
function checkHolds(handle) {
|
|
13
|
+
let factor = 1;
|
|
14
|
+
let sum = 0;
|
|
15
|
+
for (let i = handle.length - 1; i >= 0; i--) {
|
|
16
|
+
let addend = factor * HANDLE_ALPHABET.indexOf(handle[i]);
|
|
17
|
+
factor = factor === 2 ? 1 : 2;
|
|
18
|
+
addend = Math.floor(addend / N) + (addend % N);
|
|
19
|
+
sum += addend;
|
|
20
|
+
}
|
|
21
|
+
return sum % N === 0;
|
|
22
|
+
}
|
|
23
|
+
/** The handle's kind and its upper-case form, or null when it is not one. */
|
|
24
|
+
export function parseHandle(raw) {
|
|
25
|
+
const handle = raw.trim().toUpperCase();
|
|
26
|
+
if (handle.length < 7 || handle.length > 10)
|
|
27
|
+
return null;
|
|
28
|
+
if (![...handle].every((c) => HANDLE_ALPHABET.includes(c)))
|
|
29
|
+
return null;
|
|
30
|
+
const kind = LETTER_KIND[handle[0]];
|
|
31
|
+
if (!kind || !checkHolds(handle))
|
|
32
|
+
return null;
|
|
33
|
+
return { kind, id: handle };
|
|
34
|
+
}
|
|
35
|
+
const OLD_SHAPES = [
|
|
36
|
+
[/^task_[0-9a-f-]{36}$/, 'task'],
|
|
37
|
+
[/^agr_[A-Za-z0-9_]+$/, 'agreement'],
|
|
38
|
+
[/^(art_|org-state-)[A-Za-z0-9_-]+$/, 'artifact'],
|
|
39
|
+
[/^[a-z]{24}$/, 'chat'],
|
|
40
|
+
];
|
|
41
|
+
/** The shape of a handle, whether or not its check letter holds. */
|
|
42
|
+
export function looksLikeHandle(raw) {
|
|
43
|
+
return /^[TACF][0-9A-Z]{6,9}$/i.test(raw.trim());
|
|
44
|
+
}
|
|
45
|
+
function parseOne(token) {
|
|
46
|
+
const trimmed = token.trim();
|
|
47
|
+
for (const [shape, kind] of OLD_SHAPES)
|
|
48
|
+
if (shape.test(trimmed))
|
|
49
|
+
return { kind, id: trimmed };
|
|
50
|
+
return parseHandle(trimmed);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* What a `ref` names: a handle in any case, a Ziggs web URL that contains
|
|
54
|
+
* one, or an id of the older shape. Null when it names nothing.
|
|
55
|
+
*/
|
|
56
|
+
export function parseRef(raw) {
|
|
57
|
+
if (typeof raw !== 'string' || !raw.trim())
|
|
58
|
+
return null;
|
|
59
|
+
const value = raw.trim();
|
|
60
|
+
const direct = parseOne(value);
|
|
61
|
+
if (direct)
|
|
62
|
+
return direct;
|
|
63
|
+
if (!/[/?#]/.test(value))
|
|
64
|
+
return null;
|
|
65
|
+
let path = value;
|
|
66
|
+
try {
|
|
67
|
+
const url = new URL(value, 'https://ziggsai.com');
|
|
68
|
+
path = `${url.pathname}/${[...url.searchParams.values()].join('/')}`;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Not a URL; split what was given.
|
|
72
|
+
}
|
|
73
|
+
for (const segment of path.split(/[/?#&=]/).reverse()) {
|
|
74
|
+
if (!segment)
|
|
75
|
+
continue;
|
|
76
|
+
const hit = parseOne(decodeURIComponent(segment));
|
|
77
|
+
if (hit)
|
|
78
|
+
return hit;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const CANDIDATE = /\b[TACF][0-9A-HJKMNP-TV-Z]{6,9}\b/gi;
|
|
83
|
+
/**
|
|
84
|
+
* Every handle named in free text, in order and once each, so an agent that
|
|
85
|
+
* reads "look at T7K2QXD" can open it without anyone pasting a form. Words
|
|
86
|
+
* that merely look like one fail the check letter and are left out.
|
|
87
|
+
*/
|
|
88
|
+
export function findHandles(text) {
|
|
89
|
+
const out = [];
|
|
90
|
+
const seen = new Set();
|
|
91
|
+
for (const match of text.matchAll(CANDIDATE)) {
|
|
92
|
+
const hit = parseHandle(match[0]);
|
|
93
|
+
if (hit && !seen.has(hit.id)) {
|
|
94
|
+
seen.add(hit.id);
|
|
95
|
+
out.push(hit);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
@@ -31,11 +31,11 @@ export class ContextOpenClient {
|
|
|
31
31
|
return this.post('/context/access/explain', body);
|
|
32
32
|
}
|
|
33
33
|
async post(path, body) {
|
|
34
|
-
const filled = ['artifactId', 'chatId', 'taskId', 'agreementId'].filter((key) => typeof body[key] === 'string' && body[key].trim());
|
|
34
|
+
const filled = ['ref', 'artifactId', 'chatId', 'taskId', 'agreementId'].filter((key) => typeof body[key] === 'string' && body[key].trim());
|
|
35
35
|
if (filled.length !== 1) {
|
|
36
36
|
// A caller's input mistake, named in the caller's words: the shared
|
|
37
37
|
// INVALID_INPUT code, and no internal class name in the sentence.
|
|
38
|
-
throw Object.assign(new Error('Pass exactly one of artifactId, chatId, taskId, agreementId.'), { code: 'INVALID_INPUT' });
|
|
38
|
+
throw Object.assign(new Error('Pass ref (a handle such as T7K2QXD, a Ziggs URL, or an id), or exactly one of artifactId, chatId, taskId, agreementId.'), { code: 'INVALID_INPUT' });
|
|
39
39
|
}
|
|
40
40
|
const url = `${this.baseUrl || getBackendUrl()}${path}`;
|
|
41
41
|
const res = await fetch(url, {
|
package/dist/index.d.ts
CHANGED
|
@@ -32,3 +32,4 @@ export type { EngagementGuide, EngagementGuideKind, EngagementOutcome, Engagemen
|
|
|
32
32
|
export { isContextAccessRequest } from './shared/contextAccessRequest.js';
|
|
33
33
|
export { AssistantConnectionClient, type AssistantWorkspace, } from './http/AssistantConnectionClient.js';
|
|
34
34
|
export { withAssistantRequest, assistantRequestHeaders } from './http/operatorHeaders.js';
|
|
35
|
+
export { HANDLE_ALPHABET, findHandles, looksLikeHandle, parseHandle, parseRef, type HandleKind } from './handles.js';
|
package/dist/index.js
CHANGED
|
@@ -29,3 +29,5 @@ export { inboxEngagement, isEchoOrDuplicate, isHireWorkOrder, contactPath, forma
|
|
|
29
29
|
export { isContextAccessRequest } from './shared/contextAccessRequest.js';
|
|
30
30
|
export { AssistantConnectionClient, } from './http/AssistantConnectionClient.js';
|
|
31
31
|
export { withAssistantRequest, assistantRequestHeaders } from './http/operatorHeaders.js';
|
|
32
|
+
// Short handles: T…, A…, C…, F… (task, agreement, chat, artifact).
|
|
33
|
+
export { HANDLE_ALPHABET, findHandles, looksLikeHandle, parseHandle, parseRef } from './handles.js';
|