@ixo/oracle-runtime-workers 0.18.0 → 0.19.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/package.json +3 -3
- package/src/channels/auth.ts +30 -8
- package/src/channels/contract.ts +25 -12
- package/src/channels/failures.ts +3 -3
- package/src/channels/turns.ts +10 -10
- package/src/core/capability-router.ts +1 -0
- package/src/delivery/profile.ts +4 -1
- package/src/delivery/schema.ts +1 -1
- package/src/do/contracts.ts +16 -5
- package/src/do/room-mirror.ts +1 -1
- package/src/do/user-oracle-do.ts +5 -5
- package/src/shell/app.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ixo/oracle-runtime-workers",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "QiForge oracle runtime for Cloudflare Workers — Durable Object per user (SQLite in WASM over DO storage), Matrix E2EE gateway Durable Object, Hono HTTP/SSE shell.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -58,9 +58,9 @@
|
|
|
58
58
|
"wa-sqlite": "1.0.0",
|
|
59
59
|
"yjs": "13.6.32",
|
|
60
60
|
"zod": "^4.4.3",
|
|
61
|
+
"@ixo/ixo-transaction": "0.1.0",
|
|
61
62
|
"@ixo/ucan": "2.2.1",
|
|
62
|
-
"@ixo/common": "1.
|
|
63
|
-
"@ixo/ixo-transaction": "0.1.0"
|
|
63
|
+
"@ixo/common": "1.7.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@cloudflare/vitest-pool-workers": "^0.22.0",
|
package/src/channels/auth.ts
CHANGED
|
@@ -41,7 +41,8 @@ export const ChannelInvoke = defineCapability({
|
|
|
41
41
|
|
|
42
42
|
export interface ChannelAuthConfig {
|
|
43
43
|
oracleDid: string;
|
|
44
|
-
|
|
44
|
+
/** Every channel service DID allowed to invoke this deployment. */
|
|
45
|
+
channelDids: readonly string[];
|
|
45
46
|
didResolver: DIDKeyResolver;
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -85,12 +86,16 @@ export async function authenticateChannel(
|
|
|
85
86
|
);
|
|
86
87
|
const chain = result.proofChain;
|
|
87
88
|
const userDid = chain?.[0];
|
|
89
|
+
// The invoker is the one listed channel service that signed this
|
|
90
|
+
// invocation; the user's grant must be addressed to that same service.
|
|
91
|
+
const channelDid = result.invoker;
|
|
88
92
|
if (
|
|
89
93
|
!result.ok ||
|
|
90
94
|
!userDid?.startsWith('did:ixo:') ||
|
|
91
95
|
chain?.length !== 2 ||
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
!channelDid ||
|
|
97
|
+
!config.channelDids.includes(channelDid) ||
|
|
98
|
+
chain[1] !== channelDid
|
|
94
99
|
)
|
|
95
100
|
throw new ChannelError(
|
|
96
101
|
401,
|
|
@@ -106,7 +111,7 @@ export async function authenticateChannel(
|
|
|
106
111
|
!('issuer' in proof) ||
|
|
107
112
|
proof.proofs.length !== 0 ||
|
|
108
113
|
proof.issuer.did() !== userDid ||
|
|
109
|
-
proof.audience.did() !==
|
|
114
|
+
proof.audience.did() !== channelDid ||
|
|
110
115
|
proof.capabilities.length !== 1 ||
|
|
111
116
|
proof.capabilities[0]?.can !== 'ixo:channel/invoke' ||
|
|
112
117
|
proof.capabilities[0]?.with !== `ixo:channel:${input.bindingId}` ||
|
|
@@ -159,7 +164,7 @@ export async function authenticateChannel(
|
|
|
159
164
|
return {
|
|
160
165
|
userDid,
|
|
161
166
|
channel: {
|
|
162
|
-
callerDid:
|
|
167
|
+
callerDid: channelDid,
|
|
163
168
|
provider: input.provider,
|
|
164
169
|
bindingId: input.bindingId,
|
|
165
170
|
bindingRevision: input.bindingRevision,
|
|
@@ -176,13 +181,30 @@ const channelWebResolver = createWebDIDResolver({
|
|
|
176
181
|
cacheTtlMs: IXO_DID_RESOLUTION_CACHE_TTL_MS,
|
|
177
182
|
});
|
|
178
183
|
|
|
184
|
+
/**
|
|
185
|
+
* `CHANNEL_SERVICE_DID` is one DID or a comma-separated list (one channel
|
|
186
|
+
* service per messaging provider, or a rotation overlap): entries are
|
|
187
|
+
* trimmed, empty ones dropped and duplicates removed, order kept.
|
|
188
|
+
*/
|
|
189
|
+
export function parseChannelServiceDids(raw: string | undefined): string[] {
|
|
190
|
+
return [
|
|
191
|
+
...new Set(
|
|
192
|
+
(raw ?? '')
|
|
193
|
+
.split(',')
|
|
194
|
+
.map((did) => did.trim())
|
|
195
|
+
.filter((did) => did !== ''),
|
|
196
|
+
),
|
|
197
|
+
];
|
|
198
|
+
}
|
|
199
|
+
|
|
179
200
|
export function channelAuthConfig(
|
|
180
201
|
env: Pick<
|
|
181
202
|
OracleWorkerEnv,
|
|
182
203
|
'CHANNEL_SERVICE_DID' | 'BLOCKSYNC_GRAPHQL_URL' | 'ORACLE_DID'
|
|
183
204
|
>,
|
|
184
205
|
): ChannelAuthConfig {
|
|
185
|
-
|
|
206
|
+
const channelDids = parseChannelServiceDids(env.CHANNEL_SERVICE_DID);
|
|
207
|
+
if (channelDids.length === 0)
|
|
186
208
|
throw new ChannelError(
|
|
187
209
|
503,
|
|
188
210
|
'Channels are not configured',
|
|
@@ -194,10 +216,10 @@ export function channelAuthConfig(
|
|
|
194
216
|
]);
|
|
195
217
|
return {
|
|
196
218
|
oracleDid: env.ORACLE_DID,
|
|
197
|
-
|
|
219
|
+
channelDids,
|
|
198
220
|
didResolver: async (did) => {
|
|
199
221
|
if (
|
|
200
|
-
did
|
|
222
|
+
channelDids.includes(did) ||
|
|
201
223
|
did === env.ORACLE_DID ||
|
|
202
224
|
did.startsWith('did:ixo:')
|
|
203
225
|
)
|
package/src/channels/contract.ts
CHANGED
|
@@ -1,22 +1,34 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { ReplyPlan } from '../delivery/types';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* A channel provider id (`whatsapp`, later others): lower-case, starts with a
|
|
6
|
+
* letter, at most 32 characters. The runtime does not enumerate providers;
|
|
7
|
+
* the gateway and the Auth Hub do. An unknown provider is delivered with the
|
|
8
|
+
* default delivery profile.
|
|
9
|
+
*/
|
|
10
|
+
export const ChannelProviderId = z.string().regex(/^[a-z][a-z0-9_-]{0,31}$/);
|
|
11
|
+
|
|
4
12
|
export const ChannelTurnBody = z.strictObject({
|
|
5
|
-
provider:
|
|
13
|
+
provider: ChannelProviderId,
|
|
6
14
|
bindingId: z.string().regex(/^chb_[A-Za-z0-9_-]{1,120}$/),
|
|
7
15
|
bindingRevision: z.number().int().positive().safe(),
|
|
8
16
|
requestId: z.string().min(1).max(180),
|
|
9
17
|
remoteMessageRef: z.string().regex(/^hmac:[a-f0-9]{64}$/),
|
|
10
18
|
sessionId: z.string().min(1).max(255).optional(),
|
|
11
19
|
message: z.string().min(1).max(16_000),
|
|
12
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Which kind of oracle the gateway believes it addresses. `companion` is
|
|
22
|
+
* the legacy value; the gateway sends `oracle`. Both are accepted.
|
|
23
|
+
*/
|
|
24
|
+
context: z.strictObject({ kind: z.enum(['companion', 'oracle']) }),
|
|
13
25
|
});
|
|
14
26
|
|
|
15
27
|
export type ChannelTurnInput = z.infer<typeof ChannelTurnBody>;
|
|
16
28
|
|
|
17
29
|
export const ChannelOrigin = z.strictObject({
|
|
18
30
|
v: z.literal(1),
|
|
19
|
-
transport:
|
|
31
|
+
transport: ChannelProviderId,
|
|
20
32
|
binding_id: ChannelTurnBody.shape.bindingId,
|
|
21
33
|
remote_ref: ChannelTurnBody.shape.remoteMessageRef,
|
|
22
34
|
});
|
|
@@ -39,7 +51,7 @@ export function channelOrigin(
|
|
|
39
51
|
|
|
40
52
|
export interface ChannelIdentity {
|
|
41
53
|
callerDid: string;
|
|
42
|
-
provider:
|
|
54
|
+
provider: string;
|
|
43
55
|
bindingId: string;
|
|
44
56
|
bindingRevision: number;
|
|
45
57
|
}
|
|
@@ -82,7 +94,7 @@ export type ChannelErrorStatus =
|
|
|
82
94
|
* The machine-readable reason a channel turn was refused. The gateway maps
|
|
83
95
|
* a refusal by `code` first and by HTTP status second. 428 (Precondition
|
|
84
96
|
* Required) means a precondition on the user's side is missing: fix it
|
|
85
|
-
* (authorize the oracle again, finish setting up the
|
|
97
|
+
* (authorize the oracle again, finish setting up the user's oracle room) and
|
|
86
98
|
* retry; it is never a transient outage.
|
|
87
99
|
*
|
|
88
100
|
* | code | status | meaning |
|
|
@@ -93,16 +105,17 @@ export type ChannelErrorStatus =
|
|
|
93
105
|
* | `identity_mismatch` | 403 | the channel identity does not match the request |
|
|
94
106
|
* | `identity_required` | 403 | the binding check got no channel identity |
|
|
95
107
|
* | `binding_inactive` | 403 | the Auth Hub reports the binding inactive |
|
|
96
|
-
* | `channel_grant_not_tool_authority`| 403 | a channel request carried a delegation header |
|
|
97
|
-
* | `
|
|
108
|
+
* | `channel_grant_not_tool_authority`| 403 | a channel request carried a delegation header |
|
|
109
|
+
* | `storage_unauthorized` | 403 | the user's file storage rejected the oracle (final) |
|
|
110
|
+
* | `session_not_found` | 404 | the session is not the user's oracle session |
|
|
98
111
|
* | `request_conflict` | 409 | the request id already belongs to another message |
|
|
99
112
|
* | `session_conflict` | 409 | another session is bound, or the named one is gone: |
|
|
100
113
|
* | | | resend the same request without `sessionId` |
|
|
101
|
-
* | `room_not_encrypted` | 409 | the
|
|
102
|
-
* | `room_unavailable` | 409 | the oracle cannot use the
|
|
114
|
+
* | `room_not_encrypted` | 409 | the user's oracle room is not end-to-end encrypted |
|
|
115
|
+
* | `room_unavailable` | 409 | the oracle cannot use the user's oracle room (final) |
|
|
103
116
|
* | `response_expired` | 410 | the run was pruned; the request cannot run again |
|
|
104
117
|
* | `request_too_large` | 413 | the body is over 64 kB |
|
|
105
|
-
* | `room_not_ready` | 428 | the user has no
|
|
118
|
+
* | `room_not_ready` | 428 | the user has no oracle room yet |
|
|
106
119
|
* | `delegation_required` | 428 | no usable delegation to this oracle (see below) |
|
|
107
120
|
* | `rate_limited` | 429 | too many requests for this user |
|
|
108
121
|
* | `not_configured` | 503 | channels or binding validation are not configured |
|
|
@@ -161,7 +174,7 @@ export class ChannelError extends Error {
|
|
|
161
174
|
* one at once).
|
|
162
175
|
*/
|
|
163
176
|
export class StaleSessionError extends ChannelError {
|
|
164
|
-
constructor(message = '
|
|
177
|
+
constructor(message = 'Oracle session not found') {
|
|
165
178
|
super(404, message, 'session_not_found');
|
|
166
179
|
this.name = 'StaleSessionError';
|
|
167
180
|
}
|
|
@@ -195,7 +208,7 @@ export interface ChannelDelegation {
|
|
|
195
208
|
export function delegationRequiredError(): ChannelError {
|
|
196
209
|
return new ChannelError(
|
|
197
210
|
428,
|
|
198
|
-
'
|
|
211
|
+
'Delegation required: the user must authorize this oracle again',
|
|
199
212
|
'delegation_required',
|
|
200
213
|
);
|
|
201
214
|
}
|
package/src/channels/failures.ts
CHANGED
|
@@ -41,7 +41,7 @@ export function isRoomForbidden(error: unknown): boolean {
|
|
|
41
41
|
* The final refusal for a failure retrying cannot fix, or null when the
|
|
42
42
|
* failure stays a transient `503 unavailable`:
|
|
43
43
|
*
|
|
44
|
-
* - the
|
|
44
|
+
* - the user's oracle room refused the oracle → `409 room_unavailable`;
|
|
45
45
|
* - the user's file storage rejected the oracle's credentials while the
|
|
46
46
|
* owner copy loaded (`VFS_AUTH_FAILED`; `NO_VFS_DELEGATION` is a
|
|
47
47
|
* `428 delegation_required` and is handled before this) →
|
|
@@ -51,13 +51,13 @@ export function classifyChannelFailure(error: unknown): ChannelError | null {
|
|
|
51
51
|
if (parseOwnerCopyFailure(error)?.code === 'VFS_AUTH_FAILED')
|
|
52
52
|
return new ChannelError(
|
|
53
53
|
403,
|
|
54
|
-
"The user's file storage rejected the oracle; the
|
|
54
|
+
"The user's file storage rejected the oracle; the oracle cannot load the user's data",
|
|
55
55
|
'storage_unauthorized',
|
|
56
56
|
);
|
|
57
57
|
if (isRoomForbidden(error))
|
|
58
58
|
return new ChannelError(
|
|
59
59
|
409,
|
|
60
|
-
|
|
60
|
+
"The oracle cannot use the user's oracle room (it is not a member or was refused)",
|
|
61
61
|
'room_unavailable',
|
|
62
62
|
);
|
|
63
63
|
return null;
|
package/src/channels/turns.ts
CHANGED
|
@@ -23,11 +23,11 @@ interface ReceiptRow extends Record<string, string | number | null> {
|
|
|
23
23
|
/** 1 once the user message was mirrored (or there is none to mirror). */
|
|
24
24
|
user_mirrored: number;
|
|
25
25
|
/**
|
|
26
|
-
* 0 while the reply is owed, 1 once it was mirrored into the
|
|
26
|
+
* 0 while the reply is owed, 1 once it was mirrored into the user's oracle
|
|
27
27
|
* room, 2 when the run ended without a reply to mirror.
|
|
28
28
|
*/
|
|
29
29
|
reply_mirrored: number;
|
|
30
|
-
/** 1 while a mirror of this request is owed to the
|
|
30
|
+
/** 1 while a mirror of this request is owed to the user's oracle room. */
|
|
31
31
|
mirror_pending: number;
|
|
32
32
|
/** Failed mirror attempts since the last success. */
|
|
33
33
|
mirror_failures: number;
|
|
@@ -71,7 +71,7 @@ export interface ChannelTurnsHost {
|
|
|
71
71
|
/**
|
|
72
72
|
* Throws `StaleSessionError` when the object holds no such session (it
|
|
73
73
|
* was deleted), any other `ChannelError` when it is not the user's
|
|
74
|
-
* session in the encrypted
|
|
74
|
+
* session in the encrypted oracle room.
|
|
75
75
|
*/
|
|
76
76
|
assertSession(identity: TurnIdentity, sessionId: string): Promise<void>;
|
|
77
77
|
/**
|
|
@@ -86,7 +86,7 @@ export interface ChannelTurnsHost {
|
|
|
86
86
|
begin(runId: string, request: TurnRequest): Promise<RunRecord>;
|
|
87
87
|
/** The turn a run was admitted with, read back from its stored request. */
|
|
88
88
|
requestOf(record: RunRecord): TurnRequest;
|
|
89
|
-
/** Post one message into the
|
|
89
|
+
/** Post one message into the user's oracle room; rejects when it could not. */
|
|
90
90
|
mirror(
|
|
91
91
|
request: TurnRequest,
|
|
92
92
|
text: string,
|
|
@@ -119,7 +119,7 @@ type Received =
|
|
|
119
119
|
* (gateway RPCs and a homeserver read);
|
|
120
120
|
* 3. under the lock again, SQLite only: bind the session and begin the run.
|
|
121
121
|
*
|
|
122
|
-
* The
|
|
122
|
+
* The user's oracle room mirrors (the user message, then the reply) are owed by
|
|
123
123
|
* the receipt and sent in the background, never awaited by a turn or a poll.
|
|
124
124
|
*/
|
|
125
125
|
export class ChannelTurns {
|
|
@@ -257,7 +257,7 @@ export class ChannelTurns {
|
|
|
257
257
|
if (stale)
|
|
258
258
|
throw new ChannelError(
|
|
259
259
|
409,
|
|
260
|
-
'The
|
|
260
|
+
'The oracle session of this channel no longer exists; send the request again without sessionId',
|
|
261
261
|
'session_conflict',
|
|
262
262
|
);
|
|
263
263
|
throw error;
|
|
@@ -294,7 +294,7 @@ export class ChannelTurns {
|
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
/**
|
|
297
|
-
* A run ended: mirror what it still owes the
|
|
297
|
+
* A run ended: mirror what it still owes the user's oracle room (its reply)
|
|
298
298
|
* in the background. The receipt records each mirror, so nothing is sent
|
|
299
299
|
* twice; a failed one is retried by the housekeeping alarm.
|
|
300
300
|
*/
|
|
@@ -318,7 +318,7 @@ export class ChannelTurns {
|
|
|
318
318
|
|
|
319
319
|
/**
|
|
320
320
|
* The session was deleted: release every binding that pointed at it, so
|
|
321
|
-
* the binding's next message opens a new
|
|
321
|
+
* the binding's next message opens a new oracle session. Returns
|
|
322
322
|
* whether a binding was released.
|
|
323
323
|
*/
|
|
324
324
|
forgetSession(sessionId: string): Promise<boolean> {
|
|
@@ -621,7 +621,7 @@ export class ChannelTurns {
|
|
|
621
621
|
}
|
|
622
622
|
return this.respond(input, receipt, session, record);
|
|
623
623
|
}
|
|
624
|
-
// The run owes its user message to the
|
|
624
|
+
// The run owes its user message to the user's oracle room from here on;
|
|
625
625
|
// the mirror pass sends it, the turn never waits for it.
|
|
626
626
|
await this.db.run(
|
|
627
627
|
`UPDATE channel_requests SET session_id = ?, user_mirrored = 0, reply_mirrored = 0,
|
|
@@ -710,7 +710,7 @@ export class ChannelTurns {
|
|
|
710
710
|
}
|
|
711
711
|
|
|
712
712
|
/**
|
|
713
|
-
* One mirror pass over the receipts that owe the
|
|
713
|
+
* One mirror pass over the receipts that owe the user's oracle room a
|
|
714
714
|
* message. Sessions are mirrored in parallel; within a session strictly
|
|
715
715
|
* in receipt order, the user message before its reply, and a mirror that
|
|
716
716
|
* fails holds the session's later ones back until it succeeds or is
|
|
@@ -222,6 +222,7 @@ export function createCapabilityRouter(
|
|
|
222
222
|
`capability=${verdict.capability ?? '-'} ` +
|
|
223
223
|
`capabilityConfidence=${verdict.capabilityConfidence?.toFixed(2) ?? '-'} ` +
|
|
224
224
|
`wouldPreload=${list(verdict.preload)} reason=${verdict.reason} ` +
|
|
225
|
+
`calibrationProfile=${verdict.calibrationProfileRef ?? '-'} ` +
|
|
225
226
|
`latencyMs=${now() - started} provider=${evaluation.provider} model=${evaluation.model}`,
|
|
226
227
|
);
|
|
227
228
|
turn.onShadowVerdict?.(verdict.preload);
|
package/src/delivery/profile.ts
CHANGED
|
@@ -135,7 +135,10 @@ export function resolveDeliveryProfile(
|
|
|
135
135
|
const surface =
|
|
136
136
|
turn.client === 'matrix'
|
|
137
137
|
? 'matrix'
|
|
138
|
-
:
|
|
138
|
+
: // An unknown provider id uses the generic profile. `hasOwn`: a
|
|
139
|
+
// provider id such as `constructor` must not hit the prototype.
|
|
140
|
+
turn.channel?.provider &&
|
|
141
|
+
Object.hasOwn(DEFAULT_LIMITS, turn.channel.provider)
|
|
139
142
|
? turn.channel.provider
|
|
140
143
|
: 'generic';
|
|
141
144
|
const merged: ChatLimits = {
|
package/src/delivery/schema.ts
CHANGED
|
@@ -46,7 +46,7 @@ function linkLabel(title: string): string {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
|
-
* The plan as one Markdown message: what the
|
|
49
|
+
* The plan as one Markdown message: what the user's oracle room mirror of a
|
|
50
50
|
* channel turn and gateways that predate plans receive. Never stored as a
|
|
51
51
|
* run's text, which stays the model's own.
|
|
52
52
|
*/
|
package/src/do/contracts.ts
CHANGED
|
@@ -8,7 +8,10 @@ import type {
|
|
|
8
8
|
ChannelTurnOutcome,
|
|
9
9
|
} from '../channels/contract';
|
|
10
10
|
import type { TaskExecutionProfile } from '../core/execution-profile';
|
|
11
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
CapabilityRouterMode,
|
|
13
|
+
DecisionProviderName,
|
|
14
|
+
} from '@ixo/common/ai/decisions';
|
|
12
15
|
import type { TranscriptPageOptions } from './transcript';
|
|
13
16
|
import type { TurnUsage } from '../core/turn-budget';
|
|
14
17
|
import type { BotStatus, EncryptedFileInfo } from '@ixo/matrix-bot-workers-sdk';
|
|
@@ -62,6 +65,7 @@ export interface OracleWorkerEnv {
|
|
|
62
65
|
UCAN_AUTH_MAX_TTL_SECONDS?: string;
|
|
63
66
|
/** `true` restores the legacy bare-delegation authentication (off by default). */
|
|
64
67
|
UCAN_ALLOW_BARE_DELEGATION_AUTH?: string;
|
|
68
|
+
/** The channel service DID, or a comma-separated list of them. */
|
|
65
69
|
CHANNEL_SERVICE_DID?: string;
|
|
66
70
|
AUTH_HUB?: Fetcher;
|
|
67
71
|
AUTH_HUB_URL?: string;
|
|
@@ -273,10 +277,16 @@ export interface OracleWorkerEnv {
|
|
|
273
277
|
NEBIUS_API_KEY?: string;
|
|
274
278
|
|
|
275
279
|
// --- decisions ----------------------------------------------------------
|
|
276
|
-
/** Bounded semantic Decision provider; unset leaves Decisions unconfigured. `openrouter-jev` reuses `OPEN_ROUTER_API_KEY`. */
|
|
277
|
-
DECISION_PROVIDER?:
|
|
278
|
-
/**
|
|
280
|
+
/** Bounded semantic Decision provider; unset leaves Decisions unconfigured. `openrouter-jev` reuses `OPEN_ROUTER_API_KEY`; the Databricks lanes need `DATABRICKS_WORKSPACE_URL` + `DATABRICKS_TOKEN`. */
|
|
281
|
+
DECISION_PROVIDER?: DecisionProviderName;
|
|
282
|
+
/** Model id override (`typesafe/jev` on Workers AI, `typesafe/jev-1.13` on OpenRouter, the Unity Catalog model service `system.ai.openjev-qwen35-4b` for `databricks-systemone`); not used by `databricks-ai-decide`. */
|
|
279
283
|
DECISION_MODEL?: string;
|
|
284
|
+
/** Databricks workspace origin (`https://<host>`, no path or query) for both Databricks lanes. */
|
|
285
|
+
DATABRICKS_WORKSPACE_URL?: string;
|
|
286
|
+
/** Secret. Databricks workspace token for both Databricks lanes (sent as HTTP Basic `token:<token>`). */
|
|
287
|
+
DATABRICKS_TOKEN?: string;
|
|
288
|
+
/** `ai_decide` function API version for `databricks-ai-decide` (default and only accepted value `1.0`). */
|
|
289
|
+
DATABRICKS_AI_DECIDE_VERSION?: string;
|
|
280
290
|
/** Cloudflare account for `cloudflare-jev` over the REST API; not needed when the `AI` binding is declared. */
|
|
281
291
|
CLOUDFLARE_ACCOUNT_ID?: string;
|
|
282
292
|
/** Workers AI token for `cloudflare-jev` over the REST API; not needed when the `AI` binding is declared. */
|
|
@@ -354,7 +364,8 @@ export interface TurnIdentity {
|
|
|
354
364
|
/** Request the shell / gateway sends to `UserOracleDO` to run one turn. */
|
|
355
365
|
export interface TurnRequest {
|
|
356
366
|
channel?: {
|
|
357
|
-
provider
|
|
367
|
+
/** A provider id (`/^[a-z][a-z0-9_-]{0,31}$/`), e.g. `whatsapp`. */
|
|
368
|
+
provider: string;
|
|
358
369
|
bindingId: string;
|
|
359
370
|
remoteMessageRef: string;
|
|
360
371
|
};
|
package/src/do/room-mirror.ts
CHANGED
package/src/do/user-oracle-do.ts
CHANGED
|
@@ -1734,7 +1734,7 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
1734
1734
|
if (!roomId)
|
|
1735
1735
|
throw new ChannelError(
|
|
1736
1736
|
428,
|
|
1737
|
-
'
|
|
1737
|
+
"The user's oracle room is not ready",
|
|
1738
1738
|
'room_not_ready',
|
|
1739
1739
|
);
|
|
1740
1740
|
return (await this.createSession(identity, { roomId, markerTxnId }))
|
|
@@ -1753,13 +1753,13 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
1753
1753
|
)
|
|
1754
1754
|
throw new ChannelError(
|
|
1755
1755
|
404,
|
|
1756
|
-
'
|
|
1756
|
+
'Oracle session not found',
|
|
1757
1757
|
'session_not_found',
|
|
1758
1758
|
);
|
|
1759
1759
|
if (!(await this.roomIsEncrypted(roomId)))
|
|
1760
1760
|
throw new ChannelError(
|
|
1761
1761
|
409,
|
|
1762
|
-
'
|
|
1762
|
+
"The user's oracle room must be encrypted",
|
|
1763
1763
|
'room_not_encrypted',
|
|
1764
1764
|
);
|
|
1765
1765
|
},
|
|
@@ -2352,7 +2352,7 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
2352
2352
|
if (next !== null) deadlines.push(Math.max(next, now + 1000));
|
|
2353
2353
|
}
|
|
2354
2354
|
|
|
2355
|
-
// Channel requests: retry the
|
|
2355
|
+
// Channel requests: retry the user's oracle room mirrors that are due, and
|
|
2356
2356
|
// prune (bounded) receipts that never started a run and old run
|
|
2357
2357
|
// tombstones (channels/turns.ts).
|
|
2358
2358
|
if (this.channelTurns) {
|
|
@@ -4763,7 +4763,7 @@ export function createUserOracleDO(opts: UserOracleDOOptions) {
|
|
|
4763
4763
|
outcome: RunOutcome,
|
|
4764
4764
|
): Promise<void> {
|
|
4765
4765
|
// A channel run's reply (or the end of a run without one) is mirrored
|
|
4766
|
-
// into the
|
|
4766
|
+
// into the user's oracle room in the background: never awaited here, so
|
|
4767
4767
|
// the session's next queued run starts at once. Without the handle (a
|
|
4768
4768
|
// reset dropped it mid-run) the next poll or alarm mirrors it.
|
|
4769
4769
|
if (record.client === 'channel') this.channelTurns?.deliverReply();
|
package/src/shell/app.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
authenticateChannel,
|
|
3
3
|
assertActiveChannelBindingCached,
|
|
4
4
|
channelAuthConfig,
|
|
5
|
+
parseChannelServiceDids,
|
|
5
6
|
} from '../channels/auth';
|
|
6
7
|
import {
|
|
7
8
|
ChannelError,
|
|
@@ -418,7 +419,7 @@ export function createShell(
|
|
|
418
419
|
|
|
419
420
|
app.post('/channels/turn', async (c) => {
|
|
420
421
|
try {
|
|
421
|
-
if (
|
|
422
|
+
if (parseChannelServiceDids(c.env.CHANNEL_SERVICE_DID).length === 0)
|
|
422
423
|
throw new ChannelError(
|
|
423
424
|
503,
|
|
424
425
|
'Channels are not configured',
|