@namzu/sdk 5.2.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +237 -0
- package/dist/agents/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/SupervisorAgent.js +13 -0
- package/dist/agents/SupervisorAgent.js.map +1 -1
- package/dist/gateway/__tests__/completion-inbox.test.d.ts +2 -0
- package/dist/gateway/__tests__/completion-inbox.test.d.ts.map +1 -0
- package/dist/gateway/__tests__/completion-inbox.test.js +258 -0
- package/dist/gateway/__tests__/completion-inbox.test.js.map +1 -0
- package/dist/gateway/completion-inbox.d.ts +113 -0
- package/dist/gateway/completion-inbox.d.ts.map +1 -0
- package/dist/gateway/completion-inbox.js +211 -0
- package/dist/gateway/completion-inbox.js.map +1 -0
- package/dist/provider/__tests__/strict-schema.test.js +50 -2
- package/dist/provider/__tests__/strict-schema.test.js.map +1 -1
- package/dist/provider/__tests__/vendor-detail.test.d.ts +2 -0
- package/dist/provider/__tests__/vendor-detail.test.d.ts.map +1 -0
- package/dist/provider/__tests__/vendor-detail.test.js +89 -0
- package/dist/provider/__tests__/vendor-detail.test.js.map +1 -0
- package/dist/provider/errors.d.ts +38 -5
- package/dist/provider/errors.d.ts.map +1 -1
- package/dist/provider/errors.js +107 -5
- package/dist/provider/errors.js.map +1 -1
- package/dist/provider/strict-schema.d.ts.map +1 -1
- package/dist/provider/strict-schema.js +64 -8
- package/dist/provider/strict-schema.js.map +1 -1
- package/dist/public-runtime.d.ts +4 -0
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +10 -0
- package/dist/public-runtime.js.map +1 -1
- package/dist/registry/tool/__tests__/dialect.test.d.ts +2 -0
- package/dist/registry/tool/__tests__/dialect.test.d.ts.map +1 -0
- package/dist/registry/tool/__tests__/dialect.test.js +146 -0
- package/dist/registry/tool/__tests__/dialect.test.js.map +1 -0
- package/dist/registry/tool/dialect.d.ts +50 -0
- package/dist/registry/tool/dialect.d.ts.map +1 -0
- package/dist/registry/tool/dialect.js +138 -0
- package/dist/registry/tool/dialect.js.map +1 -0
- package/dist/registry/tool/execute.d.ts.map +1 -1
- package/dist/registry/tool/execute.js +34 -0
- package/dist/registry/tool/execute.js.map +1 -1
- package/dist/registry/toolset/catalog.d.ts.map +1 -1
- package/dist/registry/toolset/catalog.js +10 -5
- package/dist/registry/toolset/catalog.js.map +1 -1
- package/dist/runtime/query/__tests__/completion-notification.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/completion-notification.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/completion-notification.test.js +168 -0
- package/dist/runtime/query/__tests__/completion-notification.test.js.map +1 -0
- package/dist/runtime/query/__tests__/step-allow-list.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/step-allow-list.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/step-allow-list.test.js +171 -0
- package/dist/runtime/query/__tests__/step-allow-list.test.js.map +1 -0
- package/dist/runtime/query/__tests__/stream-recovery.test.js +6 -0
- package/dist/runtime/query/__tests__/stream-recovery.test.js.map +1 -1
- package/dist/runtime/query/executor.d.ts +14 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +20 -1
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts +9 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +1 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +74 -1
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +9 -0
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/result.d.ts.map +1 -1
- package/dist/runtime/query/result.js +6 -0
- package/dist/runtime/query/result.js.map +1 -1
- package/dist/tools/coordinator/__tests__/completion-delivery.test.d.ts +2 -0
- package/dist/tools/coordinator/__tests__/completion-delivery.test.d.ts.map +1 -0
- package/dist/tools/coordinator/__tests__/completion-delivery.test.js +255 -0
- package/dist/tools/coordinator/__tests__/completion-delivery.test.js.map +1 -0
- package/dist/tools/coordinator/__tests__/task-list.test.js +13 -7
- package/dist/tools/coordinator/__tests__/task-list.test.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts +35 -0
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +222 -16
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/types/provider/error.d.ts +20 -4
- package/dist/types/provider/error.d.ts.map +1 -1
- package/dist/types/tool/index.d.ts +13 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/SupervisorAgent.ts +14 -0
- package/src/gateway/__tests__/completion-inbox.test.ts +320 -0
- package/src/gateway/completion-inbox.ts +227 -0
- package/src/provider/__tests__/strict-schema.test.ts +58 -2
- package/src/provider/__tests__/vendor-detail.test.ts +107 -0
- package/src/provider/errors.ts +106 -5
- package/src/provider/strict-schema.ts +65 -8
- package/src/public-runtime.ts +11 -0
- package/src/registry/tool/__tests__/dialect.test.ts +200 -0
- package/src/registry/tool/dialect.ts +143 -0
- package/src/registry/tool/execute.ts +35 -0
- package/src/registry/toolset/catalog.ts +10 -5
- package/src/runtime/query/__tests__/completion-notification.test.ts +196 -0
- package/src/runtime/query/__tests__/step-allow-list.test.ts +205 -0
- package/src/runtime/query/__tests__/stream-recovery.test.ts +6 -0
- package/src/runtime/query/executor.ts +21 -1
- package/src/runtime/query/index.ts +11 -0
- package/src/runtime/query/iteration/index.ts +77 -1
- package/src/runtime/query/iteration/phases/context.ts +10 -0
- package/src/runtime/query/result.ts +6 -0
- package/src/tools/coordinator/__tests__/completion-delivery.test.ts +347 -0
- package/src/tools/coordinator/__tests__/task-list.test.ts +13 -7
- package/src/tools/coordinator/index.ts +250 -20
- package/src/types/provider/error.ts +20 -4
- package/src/types/tool/index.ts +13 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { TaskGateway, TaskHandle } from '../types/agent/gateway.js';
|
|
2
|
+
import type { TaskId } from '../types/ids/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Completions that finished with nobody left to hear them.
|
|
5
|
+
*
|
|
6
|
+
* A worker's result reaches the supervisor as the `tool_result` of the
|
|
7
|
+
* `create_task` that launched it. That works whenever the launching call is
|
|
8
|
+
* still the live path — but it is not the only way a task ends:
|
|
9
|
+
*
|
|
10
|
+
* - the launching tool hit its deadline and the executor returned
|
|
11
|
+
* *"timed out… it may still be running"* to the model. The worker then
|
|
12
|
+
* finished normally, holding a result nothing would ever read.
|
|
13
|
+
* - the task was launched in the background on purpose, so there is no
|
|
14
|
+
* call waiting on it by design.
|
|
15
|
+
*
|
|
16
|
+
* In both cases the completion exists, the gateway remembers it, and the
|
|
17
|
+
* model is never told. That is the gap this closes: the run subscribes once,
|
|
18
|
+
* every settled task lands here, and anything a tool did NOT hand over
|
|
19
|
+
* inline is drained into the transcript as a notification the next turn can
|
|
20
|
+
* read.
|
|
21
|
+
*
|
|
22
|
+
* The disambiguation is the whole design. An earlier version of the envelope
|
|
23
|
+
* path was removed (`dc16d58`) because it fired for completions the blocking
|
|
24
|
+
* tool had ALREADY delivered, so the supervisor saw every result twice —
|
|
25
|
+
* once correctly as a `tool_result`, once as an orphan envelope. Removing it
|
|
26
|
+
* fixed the duplicate and left the abandoned case with no channel at all.
|
|
27
|
+
* Claiming is what tells the two apart: a tool that delivers a completion
|
|
28
|
+
* says so, and only unclaimed completions become envelopes.
|
|
29
|
+
*
|
|
30
|
+
* It attaches through `onTaskCompleted`, which every `TaskGateway` already
|
|
31
|
+
* has, so a host gateway needs no change to take part — a host that was
|
|
32
|
+
* firing completions into a listener set with no listeners now has one.
|
|
33
|
+
*/
|
|
34
|
+
export declare class CompletionInbox {
|
|
35
|
+
private readonly unheard;
|
|
36
|
+
private readonly claimed;
|
|
37
|
+
/** Launched with nothing waiting on it, and not settled yet. */
|
|
38
|
+
private readonly outstanding;
|
|
39
|
+
private readonly arrivals;
|
|
40
|
+
private detach?;
|
|
41
|
+
/**
|
|
42
|
+
* Start listening.
|
|
43
|
+
*
|
|
44
|
+
* Returns a detach function; calling `attach` twice is a no-op rather
|
|
45
|
+
* than a second subscription, because a doubly-attached inbox would
|
|
46
|
+
* queue every completion twice and reproduce the exact duplicate this
|
|
47
|
+
* class exists to prevent.
|
|
48
|
+
*/
|
|
49
|
+
attach(gateway: TaskGateway): () => void;
|
|
50
|
+
/**
|
|
51
|
+
* Say that a task was launched with nothing waiting on it.
|
|
52
|
+
*
|
|
53
|
+
* Without this the inbox can only see completions that have already
|
|
54
|
+
* happened, and a run whose supervisor launched a background worker and
|
|
55
|
+
* then answered would settle while the worker was still going — throwing
|
|
56
|
+
* away the very result the launch existed to produce. Knowing a task is
|
|
57
|
+
* outstanding is what lets the loop hold the run open for it.
|
|
58
|
+
*/
|
|
59
|
+
expect(taskId: TaskId): void;
|
|
60
|
+
/** Whether anything is either waiting to be told or still running. */
|
|
61
|
+
get hasPendingWork(): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Wait for the next completion, or for the deadline, whichever comes first.
|
|
64
|
+
*
|
|
65
|
+
* Bounded on purpose. A worker that never finishes must not hold a run
|
|
66
|
+
* open forever, and the caller decides how long "long enough" is — the
|
|
67
|
+
* run's own budget is the only thing that knows.
|
|
68
|
+
*/
|
|
69
|
+
waitForArrival(timeoutMs: number): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Say that this completion reached the model as a `tool_result`.
|
|
72
|
+
*
|
|
73
|
+
* Idempotent, and safe to call before the completion is announced: the
|
|
74
|
+
* claim is remembered so a late announcement does not re-queue it.
|
|
75
|
+
*/
|
|
76
|
+
claim(taskId: TaskId): void;
|
|
77
|
+
/** Whether anything is waiting to be told. */
|
|
78
|
+
get hasUnheard(): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Take every unheard completion, leaving the inbox empty.
|
|
81
|
+
*
|
|
82
|
+
* Draining rather than peeking: a notification that stays queued after
|
|
83
|
+
* being delivered is the duplicate-delivery bug in a different costume.
|
|
84
|
+
*/
|
|
85
|
+
drain(): TaskHandle[];
|
|
86
|
+
/**
|
|
87
|
+
* Stop expecting a task that is never going to arrive.
|
|
88
|
+
*
|
|
89
|
+
* Cancelling is the case this exists for. `expect` puts a task on the
|
|
90
|
+
* outstanding list and only a COMPLETION takes it off, so a cancelled
|
|
91
|
+
* worker left `hasPendingWork` true for the rest of the run — and every
|
|
92
|
+
* attempt to settle then paid the full grace period waiting for a result
|
|
93
|
+
* that had been called off.
|
|
94
|
+
*/
|
|
95
|
+
forget(taskId: TaskId): void;
|
|
96
|
+
/** Stop listening. Safe to call more than once. */
|
|
97
|
+
close(): void;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The message a supervisor reads when a worker it stopped waiting for
|
|
101
|
+
* finishes.
|
|
102
|
+
*
|
|
103
|
+
* It carries the task id, because without one the model cannot say which of
|
|
104
|
+
* five workers this was, and it carries the output, because a notification
|
|
105
|
+
* that only says "done" forces exactly the follow-up call this mechanism
|
|
106
|
+
* exists to remove. Long output is truncated with the task id repeated in
|
|
107
|
+
* the truncation notice, so the full text stays one `wait_for_task` away and
|
|
108
|
+
* the model knows which id to ask for — that tool takes a `task_id` and
|
|
109
|
+
* returns immediately for a task that has already finished, where the
|
|
110
|
+
* listing takes only a state filter and could not have been followed.
|
|
111
|
+
*/
|
|
112
|
+
export declare function formatCompletionNotification(handles: readonly TaskHandle[]): string;
|
|
113
|
+
//# sourceMappingURL=completion-inbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"completion-inbox.d.ts","sourceRoot":"","sources":["../../src/gateway/completion-inbox.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AACxE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAAa,eAAe;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,gEAAgE;IAChE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAoB;IAChD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;IACjD,OAAO,CAAC,MAAM,CAAC,CAAY;IAE3B;;;;;;;OAOG;IACH,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,IAAI;IAcxC;;;;;;;;OAQG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAK5B,sEAAsE;IACtE,IAAI,cAAc,IAAI,OAAO,CAE5B;IAED;;;;;;OAMG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6BhD;;;;;OAKG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAM3B,8CAA8C;IAC9C,IAAI,UAAU,IAAI,OAAO,CAExB;IAED;;;;;OAKG;IACH,KAAK,IAAI,UAAU,EAAE;IAQrB;;;;;;;;OAQG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAQ5B,mDAAmD;IACnD,KAAK,IAAI,IAAI;CAUb;AAKD;;;;;;;;;;;;GAYG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM,CA8BnF"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Completions that finished with nobody left to hear them.
|
|
3
|
+
*
|
|
4
|
+
* A worker's result reaches the supervisor as the `tool_result` of the
|
|
5
|
+
* `create_task` that launched it. That works whenever the launching call is
|
|
6
|
+
* still the live path — but it is not the only way a task ends:
|
|
7
|
+
*
|
|
8
|
+
* - the launching tool hit its deadline and the executor returned
|
|
9
|
+
* *"timed out… it may still be running"* to the model. The worker then
|
|
10
|
+
* finished normally, holding a result nothing would ever read.
|
|
11
|
+
* - the task was launched in the background on purpose, so there is no
|
|
12
|
+
* call waiting on it by design.
|
|
13
|
+
*
|
|
14
|
+
* In both cases the completion exists, the gateway remembers it, and the
|
|
15
|
+
* model is never told. That is the gap this closes: the run subscribes once,
|
|
16
|
+
* every settled task lands here, and anything a tool did NOT hand over
|
|
17
|
+
* inline is drained into the transcript as a notification the next turn can
|
|
18
|
+
* read.
|
|
19
|
+
*
|
|
20
|
+
* The disambiguation is the whole design. An earlier version of the envelope
|
|
21
|
+
* path was removed (`dc16d58`) because it fired for completions the blocking
|
|
22
|
+
* tool had ALREADY delivered, so the supervisor saw every result twice —
|
|
23
|
+
* once correctly as a `tool_result`, once as an orphan envelope. Removing it
|
|
24
|
+
* fixed the duplicate and left the abandoned case with no channel at all.
|
|
25
|
+
* Claiming is what tells the two apart: a tool that delivers a completion
|
|
26
|
+
* says so, and only unclaimed completions become envelopes.
|
|
27
|
+
*
|
|
28
|
+
* It attaches through `onTaskCompleted`, which every `TaskGateway` already
|
|
29
|
+
* has, so a host gateway needs no change to take part — a host that was
|
|
30
|
+
* firing completions into a listener set with no listeners now has one.
|
|
31
|
+
*/
|
|
32
|
+
export class CompletionInbox {
|
|
33
|
+
unheard = new Map();
|
|
34
|
+
claimed = new Set();
|
|
35
|
+
/** Launched with nothing waiting on it, and not settled yet. */
|
|
36
|
+
outstanding = new Set();
|
|
37
|
+
arrivals = new Set();
|
|
38
|
+
detach;
|
|
39
|
+
/**
|
|
40
|
+
* Start listening.
|
|
41
|
+
*
|
|
42
|
+
* Returns a detach function; calling `attach` twice is a no-op rather
|
|
43
|
+
* than a second subscription, because a doubly-attached inbox would
|
|
44
|
+
* queue every completion twice and reproduce the exact duplicate this
|
|
45
|
+
* class exists to prevent.
|
|
46
|
+
*/
|
|
47
|
+
attach(gateway) {
|
|
48
|
+
if (this.detach)
|
|
49
|
+
return this.detach;
|
|
50
|
+
this.detach = gateway.onTaskCompleted((handle) => {
|
|
51
|
+
// A completion claimed before it was announced — a tool that
|
|
52
|
+
// finished its wait faster than the listener ran — is already
|
|
53
|
+
// delivered. Nothing to queue.
|
|
54
|
+
this.outstanding.delete(handle.taskId);
|
|
55
|
+
if (this.claimed.has(handle.taskId))
|
|
56
|
+
return;
|
|
57
|
+
this.unheard.set(handle.taskId, handle);
|
|
58
|
+
for (const wake of this.arrivals)
|
|
59
|
+
wake();
|
|
60
|
+
});
|
|
61
|
+
return this.detach;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Say that a task was launched with nothing waiting on it.
|
|
65
|
+
*
|
|
66
|
+
* Without this the inbox can only see completions that have already
|
|
67
|
+
* happened, and a run whose supervisor launched a background worker and
|
|
68
|
+
* then answered would settle while the worker was still going — throwing
|
|
69
|
+
* away the very result the launch existed to produce. Knowing a task is
|
|
70
|
+
* outstanding is what lets the loop hold the run open for it.
|
|
71
|
+
*/
|
|
72
|
+
expect(taskId) {
|
|
73
|
+
if (this.claimed.has(taskId))
|
|
74
|
+
return;
|
|
75
|
+
this.outstanding.add(taskId);
|
|
76
|
+
}
|
|
77
|
+
/** Whether anything is either waiting to be told or still running. */
|
|
78
|
+
get hasPendingWork() {
|
|
79
|
+
return this.unheard.size > 0 || this.outstanding.size > 0;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Wait for the next completion, or for the deadline, whichever comes first.
|
|
83
|
+
*
|
|
84
|
+
* Bounded on purpose. A worker that never finishes must not hold a run
|
|
85
|
+
* open forever, and the caller decides how long "long enough" is — the
|
|
86
|
+
* run's own budget is the only thing that knows.
|
|
87
|
+
*/
|
|
88
|
+
waitForArrival(timeoutMs) {
|
|
89
|
+
if (this.unheard.size > 0)
|
|
90
|
+
return Promise.resolve();
|
|
91
|
+
if (this.outstanding.size === 0)
|
|
92
|
+
return Promise.resolve();
|
|
93
|
+
return new Promise((resolve) => {
|
|
94
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
95
|
+
timer.unref?.();
|
|
96
|
+
function finish() {
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
wake.done = true;
|
|
99
|
+
resolve();
|
|
100
|
+
}
|
|
101
|
+
const wake = Object.assign(() => {
|
|
102
|
+
if (!wake.done) {
|
|
103
|
+
this.arrivals.delete(wake);
|
|
104
|
+
finish();
|
|
105
|
+
}
|
|
106
|
+
}, { done: false });
|
|
107
|
+
this.arrivals.add(wake);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Say that this completion reached the model as a `tool_result`.
|
|
112
|
+
*
|
|
113
|
+
* Idempotent, and safe to call before the completion is announced: the
|
|
114
|
+
* claim is remembered so a late announcement does not re-queue it.
|
|
115
|
+
*/
|
|
116
|
+
claim(taskId) {
|
|
117
|
+
this.claimed.add(taskId);
|
|
118
|
+
this.unheard.delete(taskId);
|
|
119
|
+
this.outstanding.delete(taskId);
|
|
120
|
+
}
|
|
121
|
+
/** Whether anything is waiting to be told. */
|
|
122
|
+
get hasUnheard() {
|
|
123
|
+
return this.unheard.size > 0;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Take every unheard completion, leaving the inbox empty.
|
|
127
|
+
*
|
|
128
|
+
* Draining rather than peeking: a notification that stays queued after
|
|
129
|
+
* being delivered is the duplicate-delivery bug in a different costume.
|
|
130
|
+
*/
|
|
131
|
+
drain() {
|
|
132
|
+
if (this.unheard.size === 0)
|
|
133
|
+
return [];
|
|
134
|
+
const handles = [...this.unheard.values()];
|
|
135
|
+
this.unheard.clear();
|
|
136
|
+
for (const handle of handles)
|
|
137
|
+
this.claimed.add(handle.taskId);
|
|
138
|
+
return handles;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Stop expecting a task that is never going to arrive.
|
|
142
|
+
*
|
|
143
|
+
* Cancelling is the case this exists for. `expect` puts a task on the
|
|
144
|
+
* outstanding list and only a COMPLETION takes it off, so a cancelled
|
|
145
|
+
* worker left `hasPendingWork` true for the rest of the run — and every
|
|
146
|
+
* attempt to settle then paid the full grace period waiting for a result
|
|
147
|
+
* that had been called off.
|
|
148
|
+
*/
|
|
149
|
+
forget(taskId) {
|
|
150
|
+
this.outstanding.delete(taskId);
|
|
151
|
+
this.unheard.delete(taskId);
|
|
152
|
+
// Anyone waiting should re-check rather than sit out their deadline
|
|
153
|
+
// for a task that is no longer coming.
|
|
154
|
+
for (const wake of [...this.arrivals])
|
|
155
|
+
wake();
|
|
156
|
+
}
|
|
157
|
+
/** Stop listening. Safe to call more than once. */
|
|
158
|
+
close() {
|
|
159
|
+
this.detach?.();
|
|
160
|
+
this.detach = undefined;
|
|
161
|
+
this.unheard.clear();
|
|
162
|
+
this.outstanding.clear();
|
|
163
|
+
// Release anyone still waiting. A closed inbox would otherwise hold
|
|
164
|
+
// them to their own deadline for a completion that can no longer come.
|
|
165
|
+
for (const wake of [...this.arrivals])
|
|
166
|
+
wake();
|
|
167
|
+
this.arrivals.clear();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/** How much of a worker's output rides in the notification itself. */
|
|
171
|
+
const NOTIFICATION_OUTPUT_LIMIT = 4_000;
|
|
172
|
+
/**
|
|
173
|
+
* The message a supervisor reads when a worker it stopped waiting for
|
|
174
|
+
* finishes.
|
|
175
|
+
*
|
|
176
|
+
* It carries the task id, because without one the model cannot say which of
|
|
177
|
+
* five workers this was, and it carries the output, because a notification
|
|
178
|
+
* that only says "done" forces exactly the follow-up call this mechanism
|
|
179
|
+
* exists to remove. Long output is truncated with the task id repeated in
|
|
180
|
+
* the truncation notice, so the full text stays one `wait_for_task` away and
|
|
181
|
+
* the model knows which id to ask for — that tool takes a `task_id` and
|
|
182
|
+
* returns immediately for a task that has already finished, where the
|
|
183
|
+
* listing takes only a state filter and could not have been followed.
|
|
184
|
+
*/
|
|
185
|
+
export function formatCompletionNotification(handles) {
|
|
186
|
+
const blocks = handles.map((handle) => {
|
|
187
|
+
const durationMs = handle.completedAt ? handle.completedAt - handle.createdAt : undefined;
|
|
188
|
+
const output = handle.result?.result ?? handle.result?.lastError ?? '';
|
|
189
|
+
const truncated = output.length > NOTIFICATION_OUTPUT_LIMIT
|
|
190
|
+
? // `wait_for_task`, not `agent_task_list` — the listing takes only a
|
|
191
|
+
// state filter, so an instruction to call it "with task_id" named
|
|
192
|
+
// a parameter that does not exist and could not be followed. On an
|
|
193
|
+
// already-finished task the wait returns immediately.
|
|
194
|
+
`${output.slice(0, NOTIFICATION_OUTPUT_LIMIT)}\n… truncated. Call wait_for_task with task_id "${handle.taskId}" for the full output.`
|
|
195
|
+
: output;
|
|
196
|
+
const lines = [
|
|
197
|
+
`task_id: ${handle.taskId}`,
|
|
198
|
+
`agent: ${handle.agentId}`,
|
|
199
|
+
`state: ${handle.state}`,
|
|
200
|
+
...(durationMs !== undefined ? [`duration_ms: ${durationMs}`] : []),
|
|
201
|
+
'',
|
|
202
|
+
truncated.length > 0 ? truncated : '(the task produced no output)',
|
|
203
|
+
];
|
|
204
|
+
return `<task-notification>\n${lines.join('\n')}\n</task-notification>`;
|
|
205
|
+
});
|
|
206
|
+
const preamble = handles.length === 1
|
|
207
|
+
? 'A task you launched has finished. This is its result — you were not waiting on it, so it arrives here rather than as a tool result.'
|
|
208
|
+
: `${handles.length} tasks you launched have finished. These are their results — you were not waiting on them, so they arrive here rather than as tool results.`;
|
|
209
|
+
return `${preamble}\n\n${blocks.join('\n\n')}`;
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=completion-inbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"completion-inbox.js","sourceRoot":"","sources":["../../src/gateway/completion-inbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -67,17 +67,65 @@ describe('the violation report names the exact path', () => {
|
|
|
67
67
|
};
|
|
68
68
|
expect(findStrictSchemaViolations(schema)).toEqual([]);
|
|
69
69
|
});
|
|
70
|
-
it('reports
|
|
70
|
+
it('reports the bounds the wire refuses, and only those', () => {
|
|
71
|
+
// Measured against the live API rather than read off a page. The first
|
|
72
|
+
// version of this list was derived from documentation and was wrong in
|
|
73
|
+
// both directions: it refused `maxLength`, which the wire accepts, and
|
|
74
|
+
// permitted `prefixItems`, which it rejects.
|
|
71
75
|
const schema = {
|
|
72
76
|
type: 'object',
|
|
73
77
|
properties: {
|
|
74
78
|
n: { type: 'integer', minimum: 0 },
|
|
75
79
|
s: { type: 'string', maxLength: 10 },
|
|
80
|
+
a: { type: 'array', items: { type: 'string' }, maxItems: 3, minItems: 1 },
|
|
76
81
|
},
|
|
77
82
|
};
|
|
78
83
|
expect(findStrictSchemaViolations(schema)
|
|
79
84
|
.map((v) => v.keyword)
|
|
80
|
-
.sort()).toEqual(['
|
|
85
|
+
.sort()).toEqual(['maxItems', 'minimum']);
|
|
86
|
+
});
|
|
87
|
+
it('leaves string length alone, because strict accepts it', () => {
|
|
88
|
+
// The false positive that would have refused tools which work.
|
|
89
|
+
expect(findStrictSchemaViolations({ s: { type: 'string', minLength: 1, maxLength: 9 } })).toEqual([]);
|
|
90
|
+
});
|
|
91
|
+
it('catches a tuple in either spelling, because strict admits neither', () => {
|
|
92
|
+
// The interaction worth pinning, and the one a `prefixItems` entry alone
|
|
93
|
+
// got wrong. This check runs at REGISTRATION, on the schema as rendered
|
|
94
|
+
// — draft-07, where a tuple is `items: [a, b]` — while the wire sees the
|
|
95
|
+
// `prefixItems` the driver converts it to. So denying only `prefixItems`
|
|
96
|
+
// was a guard that could not fire on the path that produces tuples.
|
|
97
|
+
//
|
|
98
|
+
// Measured, strict rejects both, which is why a tool that is both strict
|
|
99
|
+
// and tuple-shaped cannot be expressed at all. Converting it only
|
|
100
|
+
// changes which error comes back.
|
|
101
|
+
for (const items of [
|
|
102
|
+
{ prefixItems: [{ type: 'integer' }, { type: 'integer' }] },
|
|
103
|
+
{ items: [{ type: 'integer' }, { type: 'integer' }] },
|
|
104
|
+
]) {
|
|
105
|
+
const violations = findStrictSchemaViolations({
|
|
106
|
+
properties: { range: { type: 'array', ...items } },
|
|
107
|
+
});
|
|
108
|
+
expect(violations, JSON.stringify(items)).toHaveLength(1);
|
|
109
|
+
expect(violations[0]?.remedy).toContain('tuple cannot be expressed');
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
it('leaves an ordinary array alone, where `items` is one schema', () => {
|
|
113
|
+
// The false positive the tuple rule must not become: `items` is the
|
|
114
|
+
// normal spelling for a homogeneous array and strict accepts it. Only
|
|
115
|
+
// the array-of-schemas form is a tuple.
|
|
116
|
+
expect(findStrictSchemaViolations({ type: 'array', items: { type: 'string' } })).toEqual([]);
|
|
117
|
+
});
|
|
118
|
+
it('admits minItems at 0 or 1 and refuses it above, as the wire does', () => {
|
|
119
|
+
// A blanket denial here was a false positive with a real cost: it
|
|
120
|
+
// refuses `z.array(...).nonempty()`, which renders `minItems: 1` and
|
|
121
|
+
// which the wire accepts. The constraint is on the VALUE, and the
|
|
122
|
+
// vendor's error says so — "'minItems' values other than 0 or 1 are not
|
|
123
|
+
// supported".
|
|
124
|
+
expect(findStrictSchemaViolations({ type: 'array', minItems: 0 })).toEqual([]);
|
|
125
|
+
expect(findStrictSchemaViolations({ type: 'array', minItems: 1 })).toEqual([]);
|
|
126
|
+
const violations = findStrictSchemaViolations({ type: 'array', minItems: 2 });
|
|
127
|
+
expect(violations).toHaveLength(1);
|
|
128
|
+
expect(violations[0]?.keyword).toBe('minItems');
|
|
81
129
|
});
|
|
82
130
|
it('admits additionalProperties only as false', () => {
|
|
83
131
|
expect(findStrictSchemaViolations({ additionalProperties: false })).toEqual([]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strict-schema.test.js","sourceRoot":"","sources":["../../../src/provider/__tests__/strict-schema.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE7C,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAC/D,OAAO,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAA;AAEpF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,QAAQ,CAAC,sEAAsE,EAAE,GAAG,EAAE;IACrF,sEAAsE;IACtE,wEAAwE;IACxE,iBAAiB;IACjB,EAAE,CAAC,IAAI,CACN,eAAe,EAAE;SACf,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC;SAClC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAU,CAAC,CAClC,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE;QACtB,MAAM,UAAU,GAAG,0BAA0B,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;QACpE,MAAM,CAAC,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAC7F,EAAE,CACF,CAAA;IACF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC3C,gEAAgE;QAChE,qEAAqE;QACrE,gCAAgC;QAChC,MAAM,CAAC,eAAe,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAA;IACvF,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,2CAA2C,EAAE,GAAG,EAAE;IAC1D,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACzD,MAAM,MAAM,GAAG;YACd,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACX,UAAU,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE;aAC9D;SACD,CAAA;QAED,MAAM,CAAC,0BAA0B,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;YAClD;gBACC,IAAI,EAAE,6BAA6B;gBACnC,OAAO,EAAE,OAAO;gBAChB,MAAM,EAAE,4DAA4D;aACpE;SACD,CAAC,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACvD,MAAM,MAAM,GAAG;YACd,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACX,UAAU,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE;aAC9D;SACD,CAAA;QAED,MAAM,CAAC,0BAA0B,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACvD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"strict-schema.test.js","sourceRoot":"","sources":["../../../src/provider/__tests__/strict-schema.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vendor-detail.test.d.ts","sourceRoot":"","sources":["../../../src/provider/__tests__/vendor-detail.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { providerHttpError, redactSecrets, vendorDetail } from '../errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* The provider's own account of what was wrong, kept — and scrubbed.
|
|
5
|
+
*
|
|
6
|
+
* `ProviderRequestErrorInit` declared `detail` from the beginning and the
|
|
7
|
+
* constructor never read it, so the field existed and carried nothing. The
|
|
8
|
+
* body was read to classify and then dropped, deliberately, because an error
|
|
9
|
+
* body can echo a request and a request can carry a key.
|
|
10
|
+
*
|
|
11
|
+
* The cost of that trade showed up in production: the wire had been saying
|
|
12
|
+
* `tools.0.custom.input_schema: … must match JSON Schema draft 2020-12` and
|
|
13
|
+
* the SDK deleted the sentence, so diagnosing it took seven eliminated
|
|
14
|
+
* hypotheses and a day of downtime. Keeping the sentence and scrubbing the
|
|
15
|
+
* credential shapes is the trade that was actually available.
|
|
16
|
+
*/
|
|
17
|
+
describe('the sentence that names the broken field survives', () => {
|
|
18
|
+
it('lifts the structured message out of a vendor body', () => {
|
|
19
|
+
const body = JSON.stringify({
|
|
20
|
+
type: 'error',
|
|
21
|
+
error: {
|
|
22
|
+
type: 'invalid_request_error',
|
|
23
|
+
message: 'tools.0.custom.input_schema: JSON schema is invalid. It must match JSON Schema draft 2020-12',
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
expect(vendorDetail(body)).toBe('tools.0.custom.input_schema: JSON schema is invalid. It must match JSON Schema draft 2020-12');
|
|
27
|
+
});
|
|
28
|
+
it('reaches the error a caller actually catches', () => {
|
|
29
|
+
const err = providerHttpError({
|
|
30
|
+
providerId: 'anthropic',
|
|
31
|
+
status: 400,
|
|
32
|
+
body: JSON.stringify({ error: { message: "Schema type 'oneOf' is not supported" } }),
|
|
33
|
+
});
|
|
34
|
+
expect(err.detail).toContain('oneOf');
|
|
35
|
+
// …and the message too, so a log line that prints only the message is
|
|
36
|
+
// still enough to act on.
|
|
37
|
+
expect(err.message).toContain('oneOf');
|
|
38
|
+
});
|
|
39
|
+
it('falls back to the raw text when the body is not JSON', () => {
|
|
40
|
+
expect(vendorDetail('upstream connect error, transport failure')).toBe('upstream connect error, transport failure');
|
|
41
|
+
});
|
|
42
|
+
it('says nothing rather than something empty', () => {
|
|
43
|
+
expect(vendorDetail(undefined)).toBeUndefined();
|
|
44
|
+
expect(vendorDetail(null)).toBeUndefined();
|
|
45
|
+
expect(vendorDetail(' ')).toBeUndefined();
|
|
46
|
+
expect(vendorDetail({})).toBeUndefined();
|
|
47
|
+
});
|
|
48
|
+
it('truncates a body that is not a sentence', () => {
|
|
49
|
+
const detail = vendorDetail('x'.repeat(5_000));
|
|
50
|
+
expect(detail?.length).toBeLessThanOrEqual(401);
|
|
51
|
+
expect(detail?.endsWith('…')).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe('a credential never rides along', () => {
|
|
55
|
+
it.each([
|
|
56
|
+
['sk-ant-api03-AbCdEfGhIjKlMnOpQrStUv', 'anthropic-style key'],
|
|
57
|
+
['npm_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345', 'npm token'],
|
|
58
|
+
['ghp_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345', 'github token'],
|
|
59
|
+
['AKIAIOSFODNN7EXAMPLE', 'aws access key id'],
|
|
60
|
+
])('scrubs %s (%s)', (secret) => {
|
|
61
|
+
const scrubbed = redactSecrets(`upstream rejected token ${secret} for this request`);
|
|
62
|
+
expect(scrubbed).not.toContain(secret);
|
|
63
|
+
expect(scrubbed).toContain('[redacted]');
|
|
64
|
+
});
|
|
65
|
+
it('scrubs a bearer header the vendor echoed back', () => {
|
|
66
|
+
const scrubbed = redactSecrets('bad header: Authorization: Bearer abcdef0123456789ABCDEF');
|
|
67
|
+
expect(scrubbed).not.toContain('abcdef0123456789ABCDEF');
|
|
68
|
+
});
|
|
69
|
+
it('scrubs a credential-named JSON field without eating the rest', () => {
|
|
70
|
+
const scrubbed = redactSecrets('{"api_key":"sk-live-9999","model":"the-model-that-failed"}');
|
|
71
|
+
expect(scrubbed).not.toContain('sk-live-9999');
|
|
72
|
+
// The surrounding sentence is the whole point — scrubbing must not
|
|
73
|
+
// degrade into deleting the message.
|
|
74
|
+
expect(scrubbed).toContain('the-model-that-failed');
|
|
75
|
+
});
|
|
76
|
+
it('scrubs on the real path, not only in the helper', () => {
|
|
77
|
+
const err = providerHttpError({
|
|
78
|
+
providerId: 'anthropic',
|
|
79
|
+
status: 401,
|
|
80
|
+
body: JSON.stringify({
|
|
81
|
+
error: { message: 'invalid key sk-ant-api03-AbCdEfGhIjKlMnOpQrStUv supplied' },
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
84
|
+
expect(err.detail).not.toContain('AbCdEfGhIjKlMnOpQrStUv');
|
|
85
|
+
expect(err.detail).toContain('[redacted]');
|
|
86
|
+
expect(err.message).not.toContain('AbCdEfGhIjKlMnOpQrStUv');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
//# sourceMappingURL=vendor-detail.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vendor-detail.test.js","sourceRoot":"","sources":["../../../src/provider/__tests__/vendor-detail.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,11 +11,19 @@
|
|
|
11
11
|
*
|
|
12
12
|
* So the contract here is deliberately narrow:
|
|
13
13
|
*
|
|
14
|
-
* - the message is built from the STATUS LINE
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* survives every logger that serializes an error chain,
|
|
18
|
-
* point.
|
|
14
|
+
* - the message is built from the STATUS LINE, the classified `kind`, and the
|
|
15
|
+
* provider's own complaint in `detail` — truncated and scrubbed of anything
|
|
16
|
+
* credential-shaped. The raw body is never re-thrown and never attached as
|
|
17
|
+
* `cause`; a `cause` survives every logger that serializes an error chain,
|
|
18
|
+
* which defeats the point.
|
|
19
|
+
*
|
|
20
|
+
* The body used to be dropped entirely. That was over-corrected: a provider
|
|
21
|
+
* rejecting a request names the exact offending field, and deleting that
|
|
22
|
+
* sentence turned a one-line diagnosis into hypothesis elimination against a
|
|
23
|
+
* live API — once at the cost of a day of production downtime, while the
|
|
24
|
+
* wire had been saying `tools.0.custom.input_schema: … must match JSON
|
|
25
|
+
* Schema draft 2020-12` the entire time. Scrubbing what looks like a
|
|
26
|
+
* credential keeps the safety and returns the sentence.
|
|
19
27
|
* - `retryAfterMs` is DATA. Nothing in this module sleeps, backs off or
|
|
20
28
|
* retries. A retry loop inside a driver burns the run's wall clock and hides
|
|
21
29
|
* the failure from the layer that should decide.
|
|
@@ -35,8 +43,33 @@ export declare class ProviderRequestError extends Error {
|
|
|
35
43
|
readonly providerId: string;
|
|
36
44
|
readonly status?: number;
|
|
37
45
|
readonly retryAfterMs?: number;
|
|
46
|
+
/**
|
|
47
|
+
* What the provider said was wrong, truncated and redacted.
|
|
48
|
+
*
|
|
49
|
+
* `ProviderRequestErrorInit` has declared this field all along and the
|
|
50
|
+
* constructor never read it, so every caller that set it was writing to
|
|
51
|
+
* nothing. That is not a cosmetic gap: a provider rejecting a request
|
|
52
|
+
* usually names the exact offending field, and losing that sentence turns
|
|
53
|
+
* a one-line diagnosis into hypothesis elimination against a live API. It
|
|
54
|
+
* did — a tool schema in the wrong JSON Schema dialect cost a day of
|
|
55
|
+
* production downtime while the wire had been saying
|
|
56
|
+
* `tools.0.custom.input_schema: … must match JSON Schema draft 2020-12`
|
|
57
|
+
* the whole time.
|
|
58
|
+
*
|
|
59
|
+
* See {@link vendorDetail} for what is kept and what is scrubbed.
|
|
60
|
+
*/
|
|
61
|
+
readonly detail?: string;
|
|
38
62
|
constructor(init: ProviderRequestErrorInit);
|
|
39
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* The provider's own account of what was wrong, safe to log.
|
|
66
|
+
*
|
|
67
|
+
* Prefers the structured `error.message` a JSON body carries, because that is
|
|
68
|
+
* the field vendors put the actionable sentence in and it is bounded; falls
|
|
69
|
+
* back to the raw text. Truncated, and every credential shape replaced.
|
|
70
|
+
*/
|
|
71
|
+
export declare function vendorDetail(body: unknown): string | undefined;
|
|
72
|
+
export declare function redactSecrets(text: string): string;
|
|
40
73
|
/** Is this a classified provider failure, whichever SDK copy threw it? */
|
|
41
74
|
export declare function isProviderRequestError(err: unknown): err is ProviderRequestError;
|
|
42
75
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/provider/errors.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/provider/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AAC7F,YAAY,EACX,iBAAiB,EACjB,iBAAiB,EACjB,wBAAwB,GACxB,MAAM,4BAA4B,CAAA;AAWnC;;;;;;;GAOG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC9C,SAAgB,IAAI,EAAE,iBAAiB,CAAA;IACvC,SAAgB,UAAU,EAAE,MAAM,CAAA;IAClC,SAAgB,MAAM,CAAC,EAAE,MAAM,CAAA;IAC/B,SAAgB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrC;;;;;;;;;;;;;;OAcG;IACH,SAAgB,MAAM,CAAC,EAAE,MAAM,CAAA;gBAEnB,IAAI,EAAE,wBAAwB;CAS1C;AAsBD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAiB9D;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQlD;AAkBD,0EAA0E;AAC1E,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,oBAAoB,CAOhF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAKhF;AAwCD,sEAAsE;AACtE,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAGhF;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAChC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACtC,GAAG,GAAE,MAAmB,GACtB,MAAM,GAAG,SAAS,CAepB;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACzC,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAClB,iBAAiB,CAanB;AAyED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IAC1C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,oBAAoB,CA0CvB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACxC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,oBAAoB,CAWvB"}
|