@kortix/sdk 0.12.1 → 0.12.3

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.
Files changed (82) hide show
  1. package/dist/browser/cache/idb-sync-cache.d.ts.map +1 -1
  2. package/dist/browser/cache/idb-sync-cache.js +26 -8
  3. package/dist/browser/cache/idb-sync-cache.js.map +1 -1
  4. package/dist/browser/stores/sync-store.d.ts +12 -0
  5. package/dist/browser/stores/sync-store.d.ts.map +1 -1
  6. package/dist/browser/stores/sync-store.js +193 -40
  7. package/dist/browser/stores/sync-store.js.map +1 -1
  8. package/dist/core/client/kortix.d.ts +19 -0
  9. package/dist/core/client/kortix.d.ts.map +1 -1
  10. package/dist/core/client/kortix.js +13 -0
  11. package/dist/core/client/kortix.js.map +1 -1
  12. package/dist/core/files/client.d.ts +10 -0
  13. package/dist/core/files/client.d.ts.map +1 -1
  14. package/dist/core/files/client.js +52 -1
  15. package/dist/core/files/client.js.map +1 -1
  16. package/dist/core/http/api-client.d.ts +16 -0
  17. package/dist/core/http/api-client.d.ts.map +1 -1
  18. package/dist/core/http/api-client.js +32 -1
  19. package/dist/core/http/api-client.js.map +1 -1
  20. package/dist/core/http/auth.d.ts +7 -0
  21. package/dist/core/http/auth.d.ts.map +1 -1
  22. package/dist/core/http/auth.js +4 -2
  23. package/dist/core/http/auth.js.map +1 -1
  24. package/dist/core/rest/projects-client/channels.d.ts +4 -0
  25. package/dist/core/rest/projects-client/channels.d.ts.map +1 -1
  26. package/dist/core/rest/projects-client/channels.js.map +1 -1
  27. package/dist/core/rest/projects-client/projects.d.ts +1 -1
  28. package/dist/core/rest/projects-client/projects.d.ts.map +1 -1
  29. package/dist/core/rest/projects-client/projects.js.map +1 -1
  30. package/dist/core/rest/projects-client/secrets.d.ts +16 -0
  31. package/dist/core/rest/projects-client/secrets.d.ts.map +1 -1
  32. package/dist/core/rest/projects-client/secrets.js +3 -0
  33. package/dist/core/rest/projects-client/secrets.js.map +1 -1
  34. package/dist/core/rest/projects-client/session-costs.d.ts +131 -1
  35. package/dist/core/rest/projects-client/session-costs.d.ts.map +1 -1
  36. package/dist/core/rest/projects-client/session-costs.js +83 -4
  37. package/dist/core/rest/projects-client/session-costs.js.map +1 -1
  38. package/dist/core/rest/projects-client/session-sandbox.d.ts +8 -0
  39. package/dist/core/rest/projects-client/session-sandbox.d.ts.map +1 -1
  40. package/dist/core/rest/projects-client/session-sandbox.js.map +1 -1
  41. package/dist/core/rest/projects-client/sessions.d.ts +123 -1
  42. package/dist/core/rest/projects-client/sessions.d.ts.map +1 -1
  43. package/dist/core/rest/projects-client/sessions.js +38 -0
  44. package/dist/core/rest/projects-client/sessions.js.map +1 -1
  45. package/dist/core/rest/projects-client/setup-links.d.ts +2 -0
  46. package/dist/core/rest/projects-client/setup-links.d.ts.map +1 -1
  47. package/dist/core/rest/projects-client/setup-links.js.map +1 -1
  48. package/dist/core/runtime/client.d.ts +19 -5
  49. package/dist/core/runtime/client.d.ts.map +1 -1
  50. package/dist/core/runtime/client.js +53 -11
  51. package/dist/core/runtime/client.js.map +1 -1
  52. package/dist/core/session/message-queue.d.ts +114 -0
  53. package/dist/core/session/message-queue.d.ts.map +1 -0
  54. package/dist/core/session/message-queue.js +155 -0
  55. package/dist/core/session/message-queue.js.map +1 -0
  56. package/dist/core/session/send-queue.d.ts +70 -0
  57. package/dist/core/session/send-queue.d.ts.map +1 -0
  58. package/dist/core/session/send-queue.js +116 -0
  59. package/dist/core/session/send-queue.js.map +1 -0
  60. package/dist/kortix.esm.min.js +16 -16
  61. package/dist/kortix.global.js +16 -16
  62. package/dist/react/index.d.ts +1 -1
  63. package/dist/react/index.d.ts.map +1 -1
  64. package/dist/react/index.js +1 -1
  65. package/dist/react/index.js.map +1 -1
  66. package/dist/react/model-flatten.d.ts.map +1 -1
  67. package/dist/react/model-flatten.js +1 -1
  68. package/dist/react/model-flatten.js.map +1 -1
  69. package/dist/react/session-title-sync.js +1 -1
  70. package/dist/react/session-title-sync.js.map +1 -1
  71. package/dist/react/use-model-defaults.d.ts.map +1 -1
  72. package/dist/react/use-model-defaults.js +27 -0
  73. package/dist/react/use-model-defaults.js.map +1 -1
  74. package/dist/react/use-session-send.d.ts +23 -0
  75. package/dist/react/use-session-send.d.ts.map +1 -1
  76. package/dist/react/use-session-send.js +31 -1
  77. package/dist/react/use-session-send.js.map +1 -1
  78. package/dist/react/use-session.d.ts +2 -0
  79. package/dist/react/use-session.d.ts.map +1 -1
  80. package/dist/react/use-session.js +37 -0
  81. package/dist/react/use-session.js.map +1 -1
  82. package/package.json +9 -5
@@ -0,0 +1,155 @@
1
+ /**
2
+ * The rules a message queue obeys, as pure functions.
3
+ *
4
+ * A message typed while the agent is mid-run is held until the turn ends. That
5
+ * much is easy. What is not easy — and what three previous implementations got
6
+ * wrong — is *when* it is released and *how many times*.
7
+ *
8
+ * The failures this module exists to make impossible:
9
+ *
10
+ * - **Two drains, one message sent twice.** `claimNext` records the claim in
11
+ * the same transition that returns the item. A second claim against the
12
+ * returned state gets nothing. There is no ref to read stale, no effect
13
+ * ordering to reason about, no window between deciding to send and having
14
+ * said so.
15
+ * - **A failed head locking out every later send.** `failInFlight` moves the
16
+ * item to `failed` and leaves `pending` immediately drainable. This is the
17
+ * exact lockout that caused the client queue to be deleted wholesale in
18
+ * `67749c1f76`; it cannot recur here.
19
+ * - **A queued message sending under the wrong model.** `agent`, `model`, and
20
+ * `variant` are captured at enqueue and carried verbatim.
21
+ *
22
+ * The claimed item stays at `pending[0]` while it is in flight rather than
23
+ * being spliced out. That makes `inFlightId` a real lock (one field to check),
24
+ * makes "reorder cannot cross the in-flight slot" an index clamp rather than a
25
+ * special case, and means a crash between claim and dispatch leaves the message
26
+ * visible in the queue instead of silently gone.
27
+ *
28
+ * Framework-free, storage-free, and timer-free by construction. There is no
29
+ * `Date.now()` and no `crypto` here: ids and timestamps are inputs, so every
30
+ * transition is deterministic and testable without fakes. Persistence and the
31
+ * decision of *when* the turn ended belong to the host.
32
+ */
33
+ export function createSessionQueue() {
34
+ return { pending: [], failed: [], inFlightId: null };
35
+ }
36
+ /**
37
+ * Add a message to the tail.
38
+ *
39
+ * Always the tail — never the head, even while something is in flight and even
40
+ * if the session happens to read idle this instant. Jumping the line is what
41
+ * "Stop & send" is for, and that is an explicit user action, not a side effect
42
+ * of timing.
43
+ */
44
+ export function enqueue(state, input) {
45
+ return { ...state, pending: [...state.pending, { ...input, attempts: 0 }] };
46
+ }
47
+ /**
48
+ * Take the head for dispatch, and record the claim in the same transition.
49
+ *
50
+ * Returns `claimed: undefined` when the queue is empty or something is already
51
+ * in flight — so a caller that races itself sends once, not twice.
52
+ */
53
+ export function claimNext(state) {
54
+ if (state.inFlightId !== null)
55
+ return { state };
56
+ const head = state.pending[0];
57
+ if (!head)
58
+ return { state };
59
+ const claimed = { ...head, attempts: head.attempts + 1 };
60
+ return {
61
+ state: {
62
+ ...state,
63
+ pending: [claimed, ...state.pending.slice(1)],
64
+ inFlightId: claimed.id,
65
+ },
66
+ claimed,
67
+ };
68
+ }
69
+ /** The send landed. Drop the item and free the queue. */
70
+ export function completeInFlight(state) {
71
+ if (state.inFlightId === null)
72
+ return state;
73
+ return {
74
+ ...state,
75
+ pending: state.pending.filter((m) => m.id !== state.inFlightId),
76
+ inFlightId: null,
77
+ };
78
+ }
79
+ /**
80
+ * The send failed for good. Set it aside — do not put it back at the head.
81
+ *
82
+ * Requeueing a failure at the head is how the queue used to wedge, and it is
83
+ * also how a prompt the server already accepted gets sent a second time. The
84
+ * item lands in `failed` with its reason, and `pending` keeps moving.
85
+ */
86
+ export function failInFlight(state, error) {
87
+ if (state.inFlightId === null)
88
+ return state;
89
+ const item = state.pending.find((m) => m.id === state.inFlightId);
90
+ if (!item)
91
+ return { ...state, inFlightId: null };
92
+ return {
93
+ pending: state.pending.filter((m) => m.id !== state.inFlightId),
94
+ failed: [...state.failed, { ...item, lastError: error }],
95
+ inFlightId: null,
96
+ };
97
+ }
98
+ /** Put a failed item back at the tail, with its error cleared. */
99
+ export function retryFailed(state, id) {
100
+ const item = state.failed.find((m) => m.id === id);
101
+ if (!item)
102
+ return state;
103
+ const { lastError: _dropped, ...rest } = item;
104
+ return {
105
+ ...state,
106
+ pending: [...state.pending, rest],
107
+ failed: state.failed.filter((m) => m.id !== id),
108
+ };
109
+ }
110
+ /** Drop a message. Refuses the in-flight item — it is already on the wire. */
111
+ export function removeQueued(state, id) {
112
+ if (id === state.inFlightId)
113
+ return state;
114
+ if (state.pending.some((m) => m.id === id)) {
115
+ return { ...state, pending: state.pending.filter((m) => m.id !== id) };
116
+ }
117
+ if (state.failed.some((m) => m.id === id)) {
118
+ return { ...state, failed: state.failed.filter((m) => m.id !== id) };
119
+ }
120
+ return state;
121
+ }
122
+ /** Rewrite a queued message. Refuses the in-flight item. */
123
+ export function editQueued(state, id, text) {
124
+ if (id === state.inFlightId)
125
+ return state;
126
+ if (!state.pending.some((m) => m.id === id))
127
+ return state;
128
+ return {
129
+ ...state,
130
+ pending: state.pending.map((m) => (m.id === id ? { ...m, text } : m)),
131
+ };
132
+ }
133
+ /**
134
+ * Move a pending message to `toIndex`.
135
+ *
136
+ * `toIndex` is clamped to the movable range. While something is in flight that
137
+ * range starts at 1, so nothing can be reordered into or past the slot that is
138
+ * already sending — and the in-flight item itself cannot move at all.
139
+ */
140
+ export function reorderQueued(state, id, toIndex) {
141
+ if (id === state.inFlightId)
142
+ return state;
143
+ const from = state.pending.findIndex((m) => m.id === id);
144
+ if (from === -1)
145
+ return state;
146
+ const first = state.inFlightId === null ? 0 : 1;
147
+ const to = Math.min(Math.max(toIndex, first), state.pending.length - 1);
148
+ if (to === from)
149
+ return state;
150
+ const pending = [...state.pending];
151
+ const [moved] = pending.splice(from, 1);
152
+ pending.splice(to, 0, moved);
153
+ return { ...state, pending };
154
+ }
155
+ //# sourceMappingURL=message-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-queue.js","sourceRoot":"","sources":["../../../src/core/session/message-queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * One prompt at a time, in the order they were written.
3
+ *
4
+ * Sending while the agent is mid-run used to be fire-and-hope: the prompt went
5
+ * out immediately and whatever the runtime did with it was invisible. The
6
+ * reported symptom was "sending also without upload feels janky while agent is
7
+ * working" — nothing on screen distinguished "waiting its turn" from "lost".
8
+ *
9
+ * This holds a prompt until the session is idle, then dispatches it. Ordering
10
+ * is guaranteed HERE rather than assumed of the server, which matters because
11
+ * the endpoint Kortix currently uses (`/session/{id}/prompt_async`) makes no
12
+ * documented promise about what happens to a prompt that arrives mid-run. The
13
+ * runtime does have a durable native queue (`delivery: 'queue'` on the v2
14
+ * `/api/session/{id}/prompt` route, with `session.next.prompt.admitted` to
15
+ * confirm promotion) — moving to it is the better long-term answer and is
16
+ * tracked separately. Until then the queue lives here, with the one honest
17
+ * caveat that a queue in a tab dies with the tab.
18
+ *
19
+ * Framework-free and timer-free: `isBusy` is injected and draining is driven by
20
+ * the caller telling it the session went idle. Nothing here polls.
21
+ *
22
+ * **Superseded for host use by `./message-queue`.** This module owns dispatch
23
+ * (it holds closures and calls them), which is exactly what a host cannot
24
+ * persist, reorder, or edit — a `dispatch` closure does not survive a page
25
+ * reload. `message-queue.ts` keeps the same one-at-a-time and never-jump-the-
26
+ * line guarantees but expresses them as pure transitions over serializable
27
+ * data, so the queue can be stored, reordered, and rewritten by the user.
28
+ *
29
+ * This module stays exported and tested: it is published API. New host code
30
+ * should reach for `message-queue` instead.
31
+ */
32
+ /** Where a queued send has got to. Drives the label on the user's bubble. */
33
+ export type SendPhase = 'queued' | 'sending' | 'sent' | 'failed';
34
+ export interface QueuedSend {
35
+ /** The optimistic message this send belongs to. */
36
+ messageId: string;
37
+ /** Actually put it on the wire. Rejects on failure. */
38
+ dispatch: () => Promise<void>;
39
+ }
40
+ export interface SendQueueOptions {
41
+ /** Is the session mid-run right now? */
42
+ isBusy: (sessionId: string) => boolean;
43
+ /** Report a phase change so a host can render it. */
44
+ onPhase?: (sessionId: string, messageId: string, phase: SendPhase) => void;
45
+ }
46
+ export interface SendQueue {
47
+ /**
48
+ * Send now if the session is idle, otherwise hold it.
49
+ *
50
+ * Returns the phase the message entered, so a caller can render without
51
+ * waiting for the `onPhase` callback to come back around.
52
+ */
53
+ submit: (sessionId: string, item: QueuedSend) => 'queued' | 'sending';
54
+ /**
55
+ * The session finished a run. Dispatch the next held prompt, if any.
56
+ *
57
+ * Called by whatever observes session status — deliberately not a poll, and
58
+ * deliberately not a subscription this module owns, so it stays framework-
59
+ * free and testable without fake timers.
60
+ */
61
+ drain: (sessionId: string) => void;
62
+ /** Abandon a specific held send (the user discarded it). */
63
+ cancel: (sessionId: string, messageId: string) => boolean;
64
+ /** Abandon everything held for a session (it closed, or was cleared). */
65
+ clear: (sessionId: string) => void;
66
+ /** Message ids still waiting, oldest first. */
67
+ pending: (sessionId: string) => string[];
68
+ }
69
+ export declare function createSendQueue(options: SendQueueOptions): SendQueue;
70
+ //# sourceMappingURL=send-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send-queue.d.ts","sourceRoot":"","sources":["../../../src/core/session/send-queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,6EAA6E;AAC7E,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEjE,MAAM,WAAW,UAAU;IACzB,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,gBAAgB;IAC/B,wCAAwC;IACxC,MAAM,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IACvC,qDAAqD;IACrD,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CAC5E;AAED,MAAM,WAAW,SAAS;IACxB;;;;;OAKG;IACH,MAAM,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,KAAK,QAAQ,GAAG,SAAS,CAAC;IACtE;;;;;;OAMG;IACH,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,4DAA4D;IAC5D,MAAM,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IAC1D,yEAAyE;IACzE,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,+CAA+C;IAC/C,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;CAC1C;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAgFpE"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * One prompt at a time, in the order they were written.
3
+ *
4
+ * Sending while the agent is mid-run used to be fire-and-hope: the prompt went
5
+ * out immediately and whatever the runtime did with it was invisible. The
6
+ * reported symptom was "sending also without upload feels janky while agent is
7
+ * working" — nothing on screen distinguished "waiting its turn" from "lost".
8
+ *
9
+ * This holds a prompt until the session is idle, then dispatches it. Ordering
10
+ * is guaranteed HERE rather than assumed of the server, which matters because
11
+ * the endpoint Kortix currently uses (`/session/{id}/prompt_async`) makes no
12
+ * documented promise about what happens to a prompt that arrives mid-run. The
13
+ * runtime does have a durable native queue (`delivery: 'queue'` on the v2
14
+ * `/api/session/{id}/prompt` route, with `session.next.prompt.admitted` to
15
+ * confirm promotion) — moving to it is the better long-term answer and is
16
+ * tracked separately. Until then the queue lives here, with the one honest
17
+ * caveat that a queue in a tab dies with the tab.
18
+ *
19
+ * Framework-free and timer-free: `isBusy` is injected and draining is driven by
20
+ * the caller telling it the session went idle. Nothing here polls.
21
+ *
22
+ * **Superseded for host use by `./message-queue`.** This module owns dispatch
23
+ * (it holds closures and calls them), which is exactly what a host cannot
24
+ * persist, reorder, or edit — a `dispatch` closure does not survive a page
25
+ * reload. `message-queue.ts` keeps the same one-at-a-time and never-jump-the-
26
+ * line guarantees but expresses them as pure transitions over serializable
27
+ * data, so the queue can be stored, reordered, and rewritten by the user.
28
+ *
29
+ * This module stays exported and tested: it is published API. New host code
30
+ * should reach for `message-queue` instead.
31
+ */
32
+ export function createSendQueue(options) {
33
+ const { isBusy, onPhase } = options;
34
+ const queues = new Map();
35
+ /** Sessions with a dispatch in flight, so `drain` never doubles up. */
36
+ const inFlight = new Set();
37
+ const phase = (sessionId, messageId, next) => {
38
+ onPhase?.(sessionId, messageId, next);
39
+ };
40
+ const run = (sessionId, item) => {
41
+ inFlight.add(sessionId);
42
+ phase(sessionId, item.messageId, 'sending');
43
+ void item
44
+ .dispatch()
45
+ .then(() => {
46
+ phase(sessionId, item.messageId, 'sent');
47
+ })
48
+ .catch(() => {
49
+ // A failed send must not wedge everything written after it. The
50
+ // message keeps its own `failed` phase (the host offers a retry);
51
+ // the queue moves on.
52
+ phase(sessionId, item.messageId, 'failed');
53
+ })
54
+ .finally(() => {
55
+ inFlight.delete(sessionId);
56
+ // The run this send started has not begun yet — `drain` is driven by
57
+ // the session going idle, and it is about to go busy. Only pull the
58
+ // next item if the session never became busy at all (e.g. the send
59
+ // failed outright), otherwise the idle transition will do it.
60
+ if (!isBusy(sessionId))
61
+ drain(sessionId);
62
+ });
63
+ };
64
+ const drain = (sessionId) => {
65
+ if (inFlight.has(sessionId))
66
+ return;
67
+ if (isBusy(sessionId))
68
+ return;
69
+ const queue = queues.get(sessionId);
70
+ if (!queue || queue.length === 0)
71
+ return;
72
+ const next = queue.shift();
73
+ if (queue.length === 0)
74
+ queues.delete(sessionId);
75
+ if (next)
76
+ run(sessionId, next);
77
+ };
78
+ return {
79
+ submit: (sessionId, item) => {
80
+ if (!isBusy(sessionId) && !inFlight.has(sessionId)) {
81
+ const queue = queues.get(sessionId);
82
+ // Never jump the line: if anything is already waiting, this one waits
83
+ // behind it even though the session happens to be idle this instant.
84
+ if (!queue || queue.length === 0) {
85
+ run(sessionId, item);
86
+ return 'sending';
87
+ }
88
+ }
89
+ const queue = queues.get(sessionId);
90
+ if (queue)
91
+ queue.push(item);
92
+ else
93
+ queues.set(sessionId, [item]);
94
+ phase(sessionId, item.messageId, 'queued');
95
+ return 'queued';
96
+ },
97
+ drain,
98
+ cancel: (sessionId, messageId) => {
99
+ const queue = queues.get(sessionId);
100
+ if (!queue)
101
+ return false;
102
+ const index = queue.findIndex((q) => q.messageId === messageId);
103
+ if (index === -1)
104
+ return false;
105
+ queue.splice(index, 1);
106
+ if (queue.length === 0)
107
+ queues.delete(sessionId);
108
+ return true;
109
+ },
110
+ clear: (sessionId) => {
111
+ queues.delete(sessionId);
112
+ },
113
+ pending: (sessionId) => (queues.get(sessionId) ?? []).map((q) => q.messageId),
114
+ };
115
+ }
116
+ //# sourceMappingURL=send-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send-queue.js","sourceRoot":"","sources":["../../../src/core/session/send-queue.ts"],"names":[],"mappings":"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"}