@athenaintel/react 0.24.0 → 0.25.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/README.md +29 -4
- package/dist/assets/AssetPanel.cjs +3 -2
- package/dist/assets/AssetPanel.cjs.map +1 -1
- package/dist/assets/AssetPanel.js +3 -2
- package/dist/assets/AssetPanel.js.map +1 -1
- package/dist/assets/AthenaAssetEmbed.cjs +1 -0
- package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
- package/dist/assets/AthenaAssetEmbed.js +1 -0
- package/dist/assets/AthenaAssetEmbed.js.map +1 -1
- package/dist/assets/use-asset-embed.cjs +22 -23
- package/dist/assets/use-asset-embed.cjs.map +1 -1
- package/dist/assets/use-asset-embed.d.ts +9 -0
- package/dist/assets/use-asset-embed.js +22 -23
- package/dist/assets/use-asset-embed.js.map +1 -1
- package/dist/auth/AthenaAuthProvider.cjs +32 -4
- package/dist/auth/AthenaAuthProvider.cjs.map +1 -1
- package/dist/auth/AthenaAuthProvider.d.ts +21 -0
- package/dist/auth/AthenaAuthProvider.js +33 -5
- package/dist/auth/AthenaAuthProvider.js.map +1 -1
- package/dist/auth/AthenaCredentialSourceContext.cjs +21 -0
- package/dist/auth/AthenaCredentialSourceContext.cjs.map +1 -0
- package/dist/auth/AthenaCredentialSourceContext.d.ts +14 -0
- package/dist/auth/AthenaCredentialSourceContext.js +21 -0
- package/dist/auth/AthenaCredentialSourceContext.js.map +1 -0
- package/dist/auth/AthenaSSOProvider.cjs +71 -47
- package/dist/auth/AthenaSSOProvider.cjs.map +1 -1
- package/dist/auth/AthenaSSOProvider.js +71 -47
- package/dist/auth/AthenaSSOProvider.js.map +1 -1
- package/dist/auth/credential-source.cjs +120 -0
- package/dist/auth/credential-source.cjs.map +1 -0
- package/dist/auth/credential-source.d.ts +68 -0
- package/dist/auth/credential-source.js +120 -0
- package/dist/auth/credential-source.js.map +1 -0
- package/dist/auth/types.d.ts +9 -0
- package/dist/chat/AthenaChat.cjs +8 -0
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.js +8 -0
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/StatewireApprovalCard.cjs +31 -6
- package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
- package/dist/chat/StatewireApprovalCard.d.ts +34 -4
- package/dist/chat/StatewireApprovalCard.js +33 -8
- package/dist/chat/StatewireApprovalCard.js.map +1 -1
- package/dist/chat-ui/index.d.ts +1 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.cjs +20 -11
- package/dist/chat-ui/mentions/suggestions/data/api-client.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.d.ts +11 -2
- package/dist/chat-ui/mentions/suggestions/data/api-client.js +20 -11
- package/dist/chat-ui/mentions/suggestions/data/api-client.js.map +1 -1
- package/dist/chat-ui/statewire.d.ts +1 -1
- package/dist/collab/mint.cjs +6 -15
- package/dist/collab/mint.cjs.map +1 -1
- package/dist/collab/mint.d.ts +8 -0
- package/dist/collab/mint.js +6 -15
- package/dist/collab/mint.js.map +1 -1
- package/dist/collab/presence-mint.cjs +20 -16
- package/dist/collab/presence-mint.cjs.map +1 -1
- package/dist/collab/presence-mint.js +21 -17
- package/dist/collab/presence-mint.js.map +1 -1
- package/dist/collab/presence-react.cjs +12 -5
- package/dist/collab/presence-react.cjs.map +1 -1
- package/dist/collab/presence-react.d.ts +3 -1
- package/dist/collab/presence-react.js +12 -5
- package/dist/collab/presence-react.js.map +1 -1
- package/dist/collab/react.cjs +5 -2
- package/dist/collab/react.cjs.map +1 -1
- package/dist/collab/react.js +5 -2
- package/dist/collab/react.js.map +1 -1
- package/dist/dictation/use-athena-dictation-engine.cjs +12 -11
- package/dist/dictation/use-athena-dictation-engine.cjs.map +1 -1
- package/dist/dictation/use-athena-dictation-engine.d.ts +4 -3
- package/dist/dictation/use-athena-dictation-engine.js +12 -11
- package/dist/dictation/use-athena-dictation-engine.js.map +1 -1
- package/dist/index.cjs +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -1
- package/dist/packages/agent-runtime/src/conversation-list.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/conversation-list.js.map +1 -1
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs +1 -0
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.js +1 -0
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
- package/dist/packages/agent-runtime/src/interrupts.cjs +22 -2
- package/dist/packages/agent-runtime/src/interrupts.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/interrupts.js +22 -2
- package/dist/packages/agent-runtime/src/interrupts.js.map +1 -1
- package/dist/packages/agent-runtime/src/messages.cjs +2 -1
- package/dist/packages/agent-runtime/src/messages.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/messages.js +2 -1
- package/dist/packages/agent-runtime/src/messages.js.map +1 -1
- package/dist/packages/agent-runtime/src/voice-presentation.cjs +6 -0
- package/dist/packages/agent-runtime/src/voice-presentation.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/voice-presentation.js +6 -0
- package/dist/packages/agent-runtime/src/voice-presentation.js.map +1 -1
- package/dist/packages/agent-runtime/src/voice-turn-grouping.cjs +68 -0
- package/dist/packages/agent-runtime/src/voice-turn-grouping.cjs.map +1 -0
- package/dist/packages/agent-runtime/src/voice-turn-grouping.js +68 -0
- package/dist/packages/agent-runtime/src/voice-turn-grouping.js.map +1 -0
- package/dist/packages/react/package.json.cjs +1 -1
- package/dist/packages/react/package.json.js +1 -1
- package/dist/provider/AthenaContext.cjs.map +1 -1
- package/dist/provider/AthenaContext.d.ts +7 -1
- package/dist/provider/AthenaContext.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +24 -2
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.js +24 -2
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/provider/theme-style-context.cjs +12 -0
- package/dist/provider/theme-style-context.cjs.map +1 -0
- package/dist/provider/theme-style-context.d.ts +8 -0
- package/dist/provider/theme-style-context.js +12 -0
- package/dist/provider/theme-style-context.js.map +1 -0
- package/dist/runtime/auth-refusal-restart.cjs +66 -0
- package/dist/runtime/auth-refusal-restart.cjs.map +1 -0
- package/dist/runtime/auth-refusal-restart.d.ts +35 -0
- package/dist/runtime/auth-refusal-restart.js +66 -0
- package/dist/runtime/auth-refusal-restart.js.map +1 -0
- package/dist/runtime/auth.cjs +58 -21
- package/dist/runtime/auth.cjs.map +1 -1
- package/dist/runtime/auth.d.ts +11 -1
- package/dist/runtime/auth.js +59 -22
- package/dist/runtime/auth.js.map +1 -1
- package/dist/runtime/credential-renewal.cjs +29 -0
- package/dist/runtime/credential-renewal.cjs.map +1 -0
- package/dist/runtime/credential-renewal.d.ts +35 -0
- package/dist/runtime/credential-renewal.js +29 -0
- package/dist/runtime/credential-renewal.js.map +1 -0
- package/dist/search/useAthenaRepoSearch.cjs +3 -5
- package/dist/search/useAthenaRepoSearch.cjs.map +1 -1
- package/dist/search/useAthenaRepoSearch.d.ts +7 -7
- package/dist/search/useAthenaRepoSearch.js +3 -5
- package/dist/search/useAthenaRepoSearch.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/threads/SessionList.cjs +87 -16
- package/dist/threads/SessionList.cjs.map +1 -1
- package/dist/threads/SessionList.js +88 -17
- package/dist/threads/SessionList.js.map +1 -1
- package/dist/threads/api.cjs +5 -1
- package/dist/threads/api.cjs.map +1 -1
- package/dist/threads/api.js +5 -1
- package/dist/threads/api.js.map +1 -1
- package/dist/threads/conversation-list-fetcher.cjs +5 -5
- package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
- package/dist/threads/conversation-list-fetcher.d.ts +6 -5
- package/dist/threads/conversation-list-fetcher.js +5 -5
- package/dist/threads/conversation-list-fetcher.js.map +1 -1
- package/dist/threads/session-list-model.cjs +13 -5
- package/dist/threads/session-list-model.cjs.map +1 -1
- package/dist/threads/session-list-model.d.ts +10 -3
- package/dist/threads/session-list-model.js +13 -5
- package/dist/threads/session-list-model.js.map +1 -1
- package/dist/threads/statewire-thread-list.cjs +13 -14
- package/dist/threads/statewire-thread-list.cjs.map +1 -1
- package/dist/threads/statewire-thread-list.js +13 -14
- package/dist/threads/statewire-thread-list.js.map +1 -1
- package/dist/threads/use-rest-conversation-history.cjs +6 -2
- package/dist/threads/use-rest-conversation-history.cjs.map +1 -1
- package/dist/threads/use-rest-conversation-history.d.ts +3 -1
- package/dist/threads/use-rest-conversation-history.js +7 -3
- package/dist/threads/use-rest-conversation-history.js.map +1 -1
- package/dist/threads/use-statewire-conversation-history.cjs +9 -2
- package/dist/threads/use-statewire-conversation-history.cjs.map +1 -1
- package/dist/threads/use-statewire-conversation-history.d.ts +3 -1
- package/dist/threads/use-statewire-conversation-history.js +10 -3
- package/dist/threads/use-statewire-conversation-history.js.map +1 -1
- package/dist/threads/use-thread-archive.cjs +2 -1
- package/dist/threads/use-thread-archive.cjs.map +1 -1
- package/dist/threads/use-thread-archive.d.ts +3 -1
- package/dist/threads/use-thread-archive.js +2 -1
- package/dist/threads/use-thread-archive.js.map +1 -1
- package/dist/threads/use-thread-favorite.cjs.map +1 -1
- package/dist/threads/use-thread-favorite.d.ts +3 -1
- package/dist/threads/use-thread-favorite.js.map +1 -1
- package/dist/threads/use-thread-field-override.cjs +2 -1
- package/dist/threads/use-thread-field-override.cjs.map +1 -1
- package/dist/threads/use-thread-field-override.d.ts +3 -1
- package/dist/threads/use-thread-field-override.js +2 -1
- package/dist/threads/use-thread-field-override.js.map +1 -1
- package/dist/threads/use-thread-rename.cjs.map +1 -1
- package/dist/threads/use-thread-rename.d.ts +3 -1
- package/dist/threads/use-thread-rename.js.map +1 -1
- package/dist/tools/tool-uis.cjs +11 -15
- package/dist/tools/tool-uis.cjs.map +1 -1
- package/dist/tools/tool-uis.js +11 -15
- package/dist/tools/tool-uis.js.map +1 -1
- package/dist/ui/popover.cjs +46 -0
- package/dist/ui/popover.cjs.map +1 -0
- package/dist/ui/popover.d.ts +6 -0
- package/dist/ui/popover.js +46 -0
- package/dist/ui/popover.js.map +1 -0
- package/dist/voice/use-athena-voice-session.cjs +17 -5
- package/dist/voice/use-athena-voice-session.cjs.map +1 -1
- package/dist/voice/use-athena-voice-session.d.ts +2 -2
- package/dist/voice/use-athena-voice-session.js +17 -5
- package/dist/voice/use-athena-voice-session.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const statewireAuthHeaders = require("./statewire-auth-headers.cjs");
|
|
4
|
+
function isCredentialRefusal(error) {
|
|
5
|
+
return typeof error === "object" && error !== null && "status" in error && error.status === 401;
|
|
6
|
+
}
|
|
7
|
+
async function withCredentialRenewal(getAuth, run) {
|
|
8
|
+
const auth = await getAuth({ force: false });
|
|
9
|
+
try {
|
|
10
|
+
return await run(auth);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
if (!isCredentialRefusal(error) || !auth.token) throw error;
|
|
13
|
+
const renewed = await getAuth({ force: true });
|
|
14
|
+
if (!renewed.token || renewed.token === auth.token) throw error;
|
|
15
|
+
return run(renewed);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function sendWithCredentialRenewal(source, send) {
|
|
19
|
+
const headers = await statewireAuthHeaders.resolveStatewireAuthHeaders(source, { staleAuth: false });
|
|
20
|
+
const response = await send(headers);
|
|
21
|
+
if (response.status !== 401 || !source.getToken || !headers.Authorization) return response;
|
|
22
|
+
const renewed = await statewireAuthHeaders.resolveStatewireAuthHeaders(source, { staleAuth: true });
|
|
23
|
+
if (!renewed.Authorization || renewed.Authorization === headers.Authorization) return response;
|
|
24
|
+
return send(renewed);
|
|
25
|
+
}
|
|
26
|
+
exports.isCredentialRefusal = isCredentialRefusal;
|
|
27
|
+
exports.sendWithCredentialRenewal = sendWithCredentialRenewal;
|
|
28
|
+
exports.withCredentialRenewal = withCredentialRenewal;
|
|
29
|
+
//# sourceMappingURL=credential-renewal.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-renewal.cjs","sources":["../../src/runtime/credential-renewal.ts"],"sourcesContent":["import { type StatewireAuthSource, resolveStatewireAuthHeaders } from './statewire-auth-headers';\n\n/**\n * The credential for one request, resolved right before it is sent. `force`\n * means the previous credential was refused: renew it first. A resolver that\n * ignores the argument keeps working — it just cannot renew.\n */\nexport type CredentialResolver<TAuth> = (options?: { force?: boolean }) => TAuth | Promise<TAuth>;\n\n/** True for a failure that says the host refused the credential (HTTP 401). */\nexport function isCredentialRefusal(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'status' in error &&\n (error as { status?: unknown }).status === 401\n );\n}\n\n/**\n * Run one request with the current credential and, when the host refuses it\n * (401), renew the credential once and run the request again — the REST twin\n * of the statewire transport's `staleAuth` retry. A second refusal, and every\n * other failure (a 403 included: renewing cannot grant access), propagates\n * unchanged. A request made without a token (an API key alone) is not\n * retried, and neither is one whose renewal produced the same token (a static\n * token) or none: there is nothing new to send. A refused request never ran\n * on the server, so repeating a write is safe.\n */\nexport async function withCredentialRenewal<TAuth extends { token?: string | null }, TResult>(\n getAuth: CredentialResolver<TAuth>,\n run: (auth: TAuth) => Promise<TResult>,\n): Promise<TResult> {\n const auth = await getAuth({ force: false });\n try {\n return await run(auth);\n } catch (error) {\n if (!isCredentialRefusal(error) || !auth.token) throw error;\n const renewed = await getAuth({ force: true });\n if (!renewed.token || renewed.token === auth.token) throw error;\n return run(renewed);\n }\n}\n\n/**\n * Send one raw `fetch` with the credential headers `source` resolves and, when\n * the host refuses a bearer token (401), renew it once (`force: true`) and\n * resend with fresh headers: the statewire transport's `staleAuth` contract.\n * `send` runs once per attempt, so it builds its request body each time (a\n * `FormData` or stream is single-use). A request sent with the API key alone,\n * from a source with no live getter, or whose renewal produced the same token\n * (a static one) or none, is not resent: there is nothing new to send.\n * Returns the last response either way.\n */\nexport async function sendWithCredentialRenewal(\n source: StatewireAuthSource,\n send: (headers: Record<string, string>) => Promise<Response>,\n): Promise<Response> {\n const headers = await resolveStatewireAuthHeaders(source, { staleAuth: false });\n const response = await send(headers);\n if (response.status !== 401 || !source.getToken || !headers.Authorization) return response;\n const renewed = await resolveStatewireAuthHeaders(source, { staleAuth: true });\n if (!renewed.Authorization || renewed.Authorization === headers.Authorization) return response;\n return send(renewed);\n}\n"],"names":["resolveStatewireAuthHeaders"],"mappings":";;;AAUO,SAAS,oBAAoB,OAAyB;AAC3D,SACE,OAAO,UAAU,YACjB,UAAU,QACV,YAAY,SACX,MAA+B,WAAW;AAE/C;AAYA,eAAsB,sBACpB,SACA,KACkB;AAClB,QAAM,OAAO,MAAM,QAAQ,EAAE,OAAO,OAAO;AAC3C,MAAI;AACF,WAAO,MAAM,IAAI,IAAI;AAAA,EACvB,SAAS,OAAO;AACd,QAAI,CAAC,oBAAoB,KAAK,KAAK,CAAC,KAAK,MAAO,OAAM;AACtD,UAAM,UAAU,MAAM,QAAQ,EAAE,OAAO,MAAM;AAC7C,QAAI,CAAC,QAAQ,SAAS,QAAQ,UAAU,KAAK,MAAO,OAAM;AAC1D,WAAO,IAAI,OAAO;AAAA,EACpB;AACF;AAYA,eAAsB,0BACpB,QACA,MACmB;AACnB,QAAM,UAAU,MAAMA,qBAAAA,4BAA4B,QAAQ,EAAE,WAAW,OAAO;AAC9E,QAAM,WAAW,MAAM,KAAK,OAAO;AACnC,MAAI,SAAS,WAAW,OAAO,CAAC,OAAO,YAAY,CAAC,QAAQ,cAAe,QAAO;AAClF,QAAM,UAAU,MAAMA,qBAAAA,4BAA4B,QAAQ,EAAE,WAAW,MAAM;AAC7E,MAAI,CAAC,QAAQ,iBAAiB,QAAQ,kBAAkB,QAAQ,cAAe,QAAO;AACtF,SAAO,KAAK,OAAO;AACrB;;;;"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type StatewireAuthSource } from './statewire-auth-headers';
|
|
2
|
+
/**
|
|
3
|
+
* The credential for one request, resolved right before it is sent. `force`
|
|
4
|
+
* means the previous credential was refused: renew it first. A resolver that
|
|
5
|
+
* ignores the argument keeps working — it just cannot renew.
|
|
6
|
+
*/
|
|
7
|
+
export type CredentialResolver<TAuth> = (options?: {
|
|
8
|
+
force?: boolean;
|
|
9
|
+
}) => TAuth | Promise<TAuth>;
|
|
10
|
+
/** True for a failure that says the host refused the credential (HTTP 401). */
|
|
11
|
+
export declare function isCredentialRefusal(error: unknown): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Run one request with the current credential and, when the host refuses it
|
|
14
|
+
* (401), renew the credential once and run the request again — the REST twin
|
|
15
|
+
* of the statewire transport's `staleAuth` retry. A second refusal, and every
|
|
16
|
+
* other failure (a 403 included: renewing cannot grant access), propagates
|
|
17
|
+
* unchanged. A request made without a token (an API key alone) is not
|
|
18
|
+
* retried, and neither is one whose renewal produced the same token (a static
|
|
19
|
+
* token) or none: there is nothing new to send. A refused request never ran
|
|
20
|
+
* on the server, so repeating a write is safe.
|
|
21
|
+
*/
|
|
22
|
+
export declare function withCredentialRenewal<TAuth extends {
|
|
23
|
+
token?: string | null;
|
|
24
|
+
}, TResult>(getAuth: CredentialResolver<TAuth>, run: (auth: TAuth) => Promise<TResult>): Promise<TResult>;
|
|
25
|
+
/**
|
|
26
|
+
* Send one raw `fetch` with the credential headers `source` resolves and, when
|
|
27
|
+
* the host refuses a bearer token (401), renew it once (`force: true`) and
|
|
28
|
+
* resend with fresh headers: the statewire transport's `staleAuth` contract.
|
|
29
|
+
* `send` runs once per attempt, so it builds its request body each time (a
|
|
30
|
+
* `FormData` or stream is single-use). A request sent with the API key alone,
|
|
31
|
+
* from a source with no live getter, or whose renewal produced the same token
|
|
32
|
+
* (a static one) or none, is not resent: there is nothing new to send.
|
|
33
|
+
* Returns the last response either way.
|
|
34
|
+
*/
|
|
35
|
+
export declare function sendWithCredentialRenewal(source: StatewireAuthSource, send: (headers: Record<string, string>) => Promise<Response>): Promise<Response>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { resolveStatewireAuthHeaders } from "./statewire-auth-headers.js";
|
|
2
|
+
function isCredentialRefusal(error) {
|
|
3
|
+
return typeof error === "object" && error !== null && "status" in error && error.status === 401;
|
|
4
|
+
}
|
|
5
|
+
async function withCredentialRenewal(getAuth, run) {
|
|
6
|
+
const auth = await getAuth({ force: false });
|
|
7
|
+
try {
|
|
8
|
+
return await run(auth);
|
|
9
|
+
} catch (error) {
|
|
10
|
+
if (!isCredentialRefusal(error) || !auth.token) throw error;
|
|
11
|
+
const renewed = await getAuth({ force: true });
|
|
12
|
+
if (!renewed.token || renewed.token === auth.token) throw error;
|
|
13
|
+
return run(renewed);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function sendWithCredentialRenewal(source, send) {
|
|
17
|
+
const headers = await resolveStatewireAuthHeaders(source, { staleAuth: false });
|
|
18
|
+
const response = await send(headers);
|
|
19
|
+
if (response.status !== 401 || !source.getToken || !headers.Authorization) return response;
|
|
20
|
+
const renewed = await resolveStatewireAuthHeaders(source, { staleAuth: true });
|
|
21
|
+
if (!renewed.Authorization || renewed.Authorization === headers.Authorization) return response;
|
|
22
|
+
return send(renewed);
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
isCredentialRefusal,
|
|
26
|
+
sendWithCredentialRenewal,
|
|
27
|
+
withCredentialRenewal
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=credential-renewal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-renewal.js","sources":["../../src/runtime/credential-renewal.ts"],"sourcesContent":["import { type StatewireAuthSource, resolveStatewireAuthHeaders } from './statewire-auth-headers';\n\n/**\n * The credential for one request, resolved right before it is sent. `force`\n * means the previous credential was refused: renew it first. A resolver that\n * ignores the argument keeps working — it just cannot renew.\n */\nexport type CredentialResolver<TAuth> = (options?: { force?: boolean }) => TAuth | Promise<TAuth>;\n\n/** True for a failure that says the host refused the credential (HTTP 401). */\nexport function isCredentialRefusal(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'status' in error &&\n (error as { status?: unknown }).status === 401\n );\n}\n\n/**\n * Run one request with the current credential and, when the host refuses it\n * (401), renew the credential once and run the request again — the REST twin\n * of the statewire transport's `staleAuth` retry. A second refusal, and every\n * other failure (a 403 included: renewing cannot grant access), propagates\n * unchanged. A request made without a token (an API key alone) is not\n * retried, and neither is one whose renewal produced the same token (a static\n * token) or none: there is nothing new to send. A refused request never ran\n * on the server, so repeating a write is safe.\n */\nexport async function withCredentialRenewal<TAuth extends { token?: string | null }, TResult>(\n getAuth: CredentialResolver<TAuth>,\n run: (auth: TAuth) => Promise<TResult>,\n): Promise<TResult> {\n const auth = await getAuth({ force: false });\n try {\n return await run(auth);\n } catch (error) {\n if (!isCredentialRefusal(error) || !auth.token) throw error;\n const renewed = await getAuth({ force: true });\n if (!renewed.token || renewed.token === auth.token) throw error;\n return run(renewed);\n }\n}\n\n/**\n * Send one raw `fetch` with the credential headers `source` resolves and, when\n * the host refuses a bearer token (401), renew it once (`force: true`) and\n * resend with fresh headers: the statewire transport's `staleAuth` contract.\n * `send` runs once per attempt, so it builds its request body each time (a\n * `FormData` or stream is single-use). A request sent with the API key alone,\n * from a source with no live getter, or whose renewal produced the same token\n * (a static one) or none, is not resent: there is nothing new to send.\n * Returns the last response either way.\n */\nexport async function sendWithCredentialRenewal(\n source: StatewireAuthSource,\n send: (headers: Record<string, string>) => Promise<Response>,\n): Promise<Response> {\n const headers = await resolveStatewireAuthHeaders(source, { staleAuth: false });\n const response = await send(headers);\n if (response.status !== 401 || !source.getToken || !headers.Authorization) return response;\n const renewed = await resolveStatewireAuthHeaders(source, { staleAuth: true });\n if (!renewed.Authorization || renewed.Authorization === headers.Authorization) return response;\n return send(renewed);\n}\n"],"names":[],"mappings":";AAUO,SAAS,oBAAoB,OAAyB;AAC3D,SACE,OAAO,UAAU,YACjB,UAAU,QACV,YAAY,SACX,MAA+B,WAAW;AAE/C;AAYA,eAAsB,sBACpB,SACA,KACkB;AAClB,QAAM,OAAO,MAAM,QAAQ,EAAE,OAAO,OAAO;AAC3C,MAAI;AACF,WAAO,MAAM,IAAI,IAAI;AAAA,EACvB,SAAS,OAAO;AACd,QAAI,CAAC,oBAAoB,KAAK,KAAK,CAAC,KAAK,MAAO,OAAM;AACtD,UAAM,UAAU,MAAM,QAAQ,EAAE,OAAO,MAAM;AAC7C,QAAI,CAAC,QAAQ,SAAS,QAAQ,UAAU,KAAK,MAAO,OAAM;AAC1D,WAAO,IAAI,OAAO;AAAA,EACpB;AACF;AAYA,eAAsB,0BACpB,QACA,MACmB;AACnB,QAAM,UAAU,MAAM,4BAA4B,QAAQ,EAAE,WAAW,OAAO;AAC9E,QAAM,WAAW,MAAM,KAAK,OAAO;AACnC,MAAI,SAAS,WAAW,OAAO,CAAC,OAAO,YAAY,CAAC,QAAQ,cAAe,QAAO;AAClF,QAAM,UAAU,MAAM,4BAA4B,QAAQ,EAAE,WAAW,MAAM;AAC7E,MAAI,CAAC,QAAQ,iBAAiB,QAAQ,kBAAkB,QAAQ,cAAe,QAAO;AACtF,SAAO,KAAK,OAAO;AACrB;"}
|
|
@@ -5,7 +5,7 @@ const react = require("react");
|
|
|
5
5
|
const mint = require("../collab/mint.cjs");
|
|
6
6
|
const errors = require("../diagnostics/errors.cjs");
|
|
7
7
|
const AthenaContext = require("../provider/AthenaContext.cjs");
|
|
8
|
-
const
|
|
8
|
+
const credentialRenewal = require("../runtime/credential-renewal.cjs");
|
|
9
9
|
const credentialShape = require("../threads/credential-shape.cjs");
|
|
10
10
|
const ATHENA_REPO_SEARCH_PATH = "/api/search/repositories";
|
|
11
11
|
function repoSearchRequestBody(params) {
|
|
@@ -24,8 +24,7 @@ function isAbortError(error) {
|
|
|
24
24
|
async function fetchRepoSearch(backendUrl, auth, params, opts = {}) {
|
|
25
25
|
const url = `${mint.getAthenaApiBaseUrl(backendUrl)}${ATHENA_REPO_SEARCH_PATH}`;
|
|
26
26
|
const body = JSON.stringify(repoSearchRequestBody(params));
|
|
27
|
-
const post = async (
|
|
28
|
-
const headers = await statewireAuthHeaders.resolveStatewireAuthHeaders(auth, { staleAuth });
|
|
27
|
+
const post = async (headers) => {
|
|
29
28
|
try {
|
|
30
29
|
return await fetch(url, {
|
|
31
30
|
method: "POST",
|
|
@@ -43,8 +42,7 @@ async function fetchRepoSearch(backendUrl, auth, params, opts = {}) {
|
|
|
43
42
|
});
|
|
44
43
|
}
|
|
45
44
|
};
|
|
46
|
-
|
|
47
|
-
if (res.status === 401 && auth.getToken) res = await post(true);
|
|
45
|
+
const res = await credentialRenewal.sendWithCredentialRenewal(auth, post);
|
|
48
46
|
if (!res.ok) {
|
|
49
47
|
const refused = res.status === 401 || res.status === 403;
|
|
50
48
|
throw new errors.AthenaSdkError({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAthenaRepoSearch.cjs","sources":["../../src/search/useAthenaRepoSearch.ts"],"sourcesContent":["/**\n * Code search over the workspace's indexed repositories —\n * `POST /api/search/repositories` (agora `web/api/search/repositories.py`),\n * the HTTP face of Clio's repository source for a Devin-style \"Ask\" surface\n * that searches the monorepo without an agent turn. Athena platform staff\n * only (the account-level `is_admin`, not a workspace role) on the managed\n * cloud today: a non-staff caller reads `403`, a private deployment `404`.\n */\nimport {\n keepPreviousData,\n QueryClient,\n QueryClientContext,\n useQuery,\n type UseQueryResult,\n} from '@tanstack/react-query';\nimport { useContext, useRef, useState } from 'react';\nimport { getAthenaApiBaseUrl } from '../collab/mint';\nimport { AthenaSdkError } from '../diagnostics/errors';\nimport { useAthenaConfig } from '../provider/AthenaContext';\nimport {\n resolveStatewireAuthHeaders,\n type StatewireAuthSource,\n} from '../runtime/statewire-auth-headers';\nimport { credentialShape, sameCredentialShape } from '../threads/credential-shape';\n\nexport type AthenaRepoSearchMode = 'hybrid' | 'lexical';\n\n/** One file range of one indexed repository, as the route serves it. */\nexport interface AthenaRepoSearchHit {\n /** The GitHub catalog asset the repository is connected through. */\n catalogAssetId: string;\n /** `owner/repo`; `null` when the catalog names no repository. */\n repoFullName: string | null;\n /** Repo-relative, `/`-separated. */\n path: string;\n /** 1-based, inclusive. */\n startLine: number;\n endLine: number;\n /** The enclosing declaration (`Class.method`) when the chunk's header named one. */\n symbol: string | null;\n /** Clio's language name for the file (`python`, `typescript`, …). */\n language: string | null;\n /** `code` (an AST chunk) or `window` (a Markdown heading window). */\n unitType: string;\n score: number;\n /** The indexed commit the hit was read at. */\n commitSha: string | null;\n /** `{origin}/{owner}/{repo}/blob/{sha}/{path}#L{a}-L{b}`, GHES-aware. */\n githubUrl: string | null;\n /** The chunk's lines, header line removed, line breaks preserved. */\n snippet: string;\n /** `asset_<catalog>?anchor=file_line_range&…` — what `read_asset` and a citation take. */\n readAssetReference: string;\n}\n\nexport interface AthenaRepoSearchResponse {\n /** Best first, at most `limit`. */\n hits: AthenaRepoSearchHit[];\n tookMs: number;\n mode: AthenaRepoSearchMode;\n}\n\nexport interface AthenaRepoSearchParams {\n query: string;\n /** 1–25; the route's default is 10. */\n limit?: number;\n /** `hybrid` (default) or `lexical`. */\n mode?: AthenaRepoSearchMode;\n /** Search these GitHub catalog assets only; empty means every indexed repository. */\n repositoryAssetIds?: string[];\n /** gitignore-style globs a hit's path must match (`agora/**\\/*.py`, `docs/`). */\n paths?: string[];\n /** Clio language names a hit's file must be indexed as. */\n languages?: string[];\n}\n\nexport interface UseAthenaRepoSearchOptions extends AthenaRepoSearchParams {\n /** `false` holds the query idle (a blank `query` does too). Default `true`. */\n enabled?: boolean;\n}\n\n/** The route's JSON body — snake_case, unset filters omitted. */\nexport interface AthenaRepoSearchRequestBody {\n query: string;\n limit?: number;\n mode?: AthenaRepoSearchMode;\n repository_asset_ids?: string[];\n paths?: string[];\n languages?: string[];\n}\n\nexport const ATHENA_REPO_SEARCH_PATH = '/api/search/repositories';\n\nexport function repoSearchRequestBody(params: AthenaRepoSearchParams): AthenaRepoSearchRequestBody {\n const body: AthenaRepoSearchRequestBody = { query: params.query.trim() };\n if (params.limit !== undefined) body.limit = params.limit;\n if (params.mode !== undefined) body.mode = params.mode;\n if (params.repositoryAssetIds?.length) body.repository_asset_ids = params.repositoryAssetIds;\n if (params.paths?.length) body.paths = params.paths;\n if (params.languages?.length) body.languages = params.languages;\n return body;\n}\n\n/** A `fetch` the caller cancelled (unmount, a newer query): lifecycle, not failure. */\nfunction isAbortError(error: unknown): boolean {\n return (error instanceof DOMException || error instanceof Error) && error.name === 'AbortError';\n}\n\n/**\n * One search request. The credential rides `Authorization: Bearer` (a token,\n * resolved live through `getToken` when the host provided one) or\n * `X-API-KEY`; with neither, a same-origin host's session cookie rides\n * automatically, as the SDK's other REST helpers allow. A `401` gets one\n * forced re-mint through `getToken` (`force: true`, the statewire\n * transport's `staleAuth` contract) — a getter that caches until forced would\n * otherwise resend the same expired token on every retry. Every failure is an\n * `AthenaSdkError`, transport rejections included; only an abort keeps its\n * own identity.\n */\nexport async function fetchRepoSearch(\n backendUrl: string,\n auth: StatewireAuthSource,\n params: AthenaRepoSearchParams,\n opts: { signal?: AbortSignal } = {},\n): Promise<AthenaRepoSearchResponse> {\n const url = `${getAthenaApiBaseUrl(backendUrl)}${ATHENA_REPO_SEARCH_PATH}`;\n const body = JSON.stringify(repoSearchRequestBody(params));\n const post = async (staleAuth: boolean): Promise<Response> => {\n const headers = await resolveStatewireAuthHeaders(auth, { staleAuth });\n try {\n return await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body,\n signal: opts.signal ?? null,\n });\n } catch (error) {\n if (isAbortError(error)) throw error;\n throw new AthenaSdkError({\n code: 'repo_search_failed',\n message: 'Repository search could not reach the Agora API.',\n cause: error,\n hint: 'Check the network and that backendUrl points at the Agora API base.',\n });\n }\n };\n let res = await post(false);\n if (res.status === 401 && auth.getToken) res = await post(true);\n if (!res.ok) {\n const refused = res.status === 401 || res.status === 403;\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'repo_search_failed',\n message: `Repository search failed: ${res.status}`,\n status: res.status,\n detail: await res.json().catch(() => undefined),\n hint: refused\n ? 'Repository search is limited to Athena platform staff on the managed cloud; check the credential.'\n : res.status === 404\n ? 'Repository search is not offered on this deployment (managed cloud only).'\n : 'Check backendUrl points at the Agora API base and that Clio is available.',\n });\n }\n return (await res.json()) as AthenaRepoSearchResponse;\n}\n\n/**\n * Search the indexed repositories through TanStack Query with the provider's\n * `backendUrl` and credentials. Keyed by the non-secret request and a\n * credential generation (a counter that advances when the host's credentials\n * change), never by the credential itself. Uses the host's `QueryClient` when\n * one is in context (`enableThreadList` mounts the SDK's own) and a\n * hook-scoped client otherwise, so it works under any `<AthenaProvider>`.\n */\nexport function useAthenaRepoSearch({\n enabled = true,\n ...params\n}: UseAthenaRepoSearchOptions): UseQueryResult<AthenaRepoSearchResponse, Error> {\n const config = useAthenaConfig();\n const { backendUrl, apiKey, getToken } = config;\n const credentialsRef = useRef(credentialShape({ apiKey, token: config.token, getToken }));\n const generationRef = useRef(0);\n const currentCredentials = credentialShape({ apiKey, token: config.token, getToken });\n if (!sameCredentialShape(credentialsRef.current, currentCredentials)) {\n credentialsRef.current = currentCredentials;\n generationRef.current += 1;\n }\n const body = repoSearchRequestBody(params);\n const contextClient = useContext(QueryClientContext);\n const [fallbackClient] = useState(() => (contextClient ? undefined : new QueryClient()));\n return useQuery(\n {\n queryKey: ['athena-sdk', 'repo-search', backendUrl, generationRef.current, body],\n // `config.token` is read at fetch time: the provider serves the latest\n // token through a getter, so rotations reach the request.\n queryFn: ({ signal }) =>\n fetchRepoSearch(backendUrl, { apiKey, token: config.token, getToken }, params, { signal }),\n enabled: enabled && body.query.length > 0 && backendUrl.length > 0,\n staleTime: 30_000,\n placeholderData: keepPreviousData,\n refetchOnWindowFocus: false,\n },\n contextClient ?? fallbackClient,\n );\n}\n"],"names":["getAthenaApiBaseUrl","resolveStatewireAuthHeaders","AthenaSdkError","useAthenaConfig","useRef","credentialShape","sameCredentialShape","useContext","QueryClientContext","useState","QueryClient","useQuery","keepPreviousData"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useAthenaRepoSearch.cjs","sources":["../../src/search/useAthenaRepoSearch.ts"],"sourcesContent":["/**\n * Code search over the workspace's indexed repositories —\n * `POST /api/search/repositories` (agora `web/api/search/repositories.py`),\n * the HTTP face of Clio's repository source for a Devin-style \"Ask\" surface\n * that searches the monorepo without an agent turn. Athena platform staff\n * only (the account-level `is_admin`, not a workspace role) on the managed\n * cloud today: a non-staff caller reads `403`, a private deployment `404`.\n */\nimport {\n keepPreviousData,\n QueryClient,\n QueryClientContext,\n useQuery,\n type UseQueryResult,\n} from '@tanstack/react-query';\nimport { useContext, useRef, useState } from 'react';\nimport { getAthenaApiBaseUrl } from '../collab/mint';\nimport { AthenaSdkError } from '../diagnostics/errors';\nimport { useAthenaConfig } from '../provider/AthenaContext';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport type { StatewireAuthSource } from '../runtime/statewire-auth-headers';\nimport { credentialShape, sameCredentialShape } from '../threads/credential-shape';\n\nexport type AthenaRepoSearchMode = 'hybrid' | 'lexical';\n\n/** One file range of one indexed repository, as the route serves it. */\nexport interface AthenaRepoSearchHit {\n /** The GitHub catalog asset the repository is connected through. */\n catalogAssetId: string;\n /** `owner/repo`; `null` when the catalog names no repository. */\n repoFullName: string | null;\n /** Repo-relative, `/`-separated. */\n path: string;\n /** 1-based, inclusive. */\n startLine: number;\n endLine: number;\n /** The enclosing declaration (`Class.method`) when the chunk's header named one. */\n symbol: string | null;\n /** Clio's language name for the file (`python`, `typescript`, …). */\n language: string | null;\n /** `code` (an AST chunk) or `window` (a Markdown heading window). */\n unitType: string;\n score: number;\n /** The indexed commit the hit was read at. */\n commitSha: string | null;\n /** `{origin}/{owner}/{repo}/blob/{sha}/{path}#L{a}-L{b}`, GHES-aware. */\n githubUrl: string | null;\n /** The chunk's lines, header line removed, line breaks preserved. */\n snippet: string;\n /** `asset_<catalog>?anchor=file_line_range&…` — what `read_asset` and a citation take. */\n readAssetReference: string;\n}\n\nexport interface AthenaRepoSearchResponse {\n /** Best first, at most `limit`. */\n hits: AthenaRepoSearchHit[];\n tookMs: number;\n mode: AthenaRepoSearchMode;\n}\n\nexport interface AthenaRepoSearchParams {\n query: string;\n /** 1–25; the route's default is 10. */\n limit?: number;\n /** `hybrid` (default) or `lexical`. */\n mode?: AthenaRepoSearchMode;\n /** Search these GitHub catalog assets only; empty means every indexed repository. */\n repositoryAssetIds?: string[];\n /** gitignore-style globs a hit's path must match (`agora/**\\/*.py`, `docs/`). */\n paths?: string[];\n /** Clio language names a hit's file must be indexed as. */\n languages?: string[];\n}\n\nexport interface UseAthenaRepoSearchOptions extends AthenaRepoSearchParams {\n /** `false` holds the query idle (a blank `query` does too). Default `true`. */\n enabled?: boolean;\n}\n\n/** The route's JSON body — snake_case, unset filters omitted. */\nexport interface AthenaRepoSearchRequestBody {\n query: string;\n limit?: number;\n mode?: AthenaRepoSearchMode;\n repository_asset_ids?: string[];\n paths?: string[];\n languages?: string[];\n}\n\nexport const ATHENA_REPO_SEARCH_PATH = '/api/search/repositories';\n\nexport function repoSearchRequestBody(params: AthenaRepoSearchParams): AthenaRepoSearchRequestBody {\n const body: AthenaRepoSearchRequestBody = { query: params.query.trim() };\n if (params.limit !== undefined) body.limit = params.limit;\n if (params.mode !== undefined) body.mode = params.mode;\n if (params.repositoryAssetIds?.length) body.repository_asset_ids = params.repositoryAssetIds;\n if (params.paths?.length) body.paths = params.paths;\n if (params.languages?.length) body.languages = params.languages;\n return body;\n}\n\n/** A `fetch` the caller cancelled (unmount, a newer query): lifecycle, not failure. */\nfunction isAbortError(error: unknown): boolean {\n return (error instanceof DOMException || error instanceof Error) && error.name === 'AbortError';\n}\n\n/**\n * One search request. The credential rides `Authorization: Bearer` (a token,\n * resolved live through `getToken` when the host provided one) or\n * `X-API-KEY`; with neither, a same-origin host's session cookie rides\n * automatically, as the SDK's other REST helpers allow. A refused bearer\n * (`401`) gets one forced re-mint through `getToken` (`force: true`, the\n * statewire transport's `staleAuth` contract; `sendWithCredentialRenewal`) — a\n * getter that caches until forced would otherwise resend the same expired\n * token on every retry. Every failure is an `AthenaSdkError`, transport\n * rejections included; only an abort keeps its own identity.\n */\nexport async function fetchRepoSearch(\n backendUrl: string,\n auth: StatewireAuthSource,\n params: AthenaRepoSearchParams,\n opts: { signal?: AbortSignal } = {},\n): Promise<AthenaRepoSearchResponse> {\n const url = `${getAthenaApiBaseUrl(backendUrl)}${ATHENA_REPO_SEARCH_PATH}`;\n const body = JSON.stringify(repoSearchRequestBody(params));\n const post = async (headers: Record<string, string>): Promise<Response> => {\n try {\n return await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body,\n signal: opts.signal ?? null,\n });\n } catch (error) {\n if (isAbortError(error)) throw error;\n throw new AthenaSdkError({\n code: 'repo_search_failed',\n message: 'Repository search could not reach the Agora API.',\n cause: error,\n hint: 'Check the network and that backendUrl points at the Agora API base.',\n });\n }\n };\n const res = await sendWithCredentialRenewal(auth, post);\n if (!res.ok) {\n const refused = res.status === 401 || res.status === 403;\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'repo_search_failed',\n message: `Repository search failed: ${res.status}`,\n status: res.status,\n detail: await res.json().catch(() => undefined),\n hint: refused\n ? 'Repository search is limited to Athena platform staff on the managed cloud; check the credential.'\n : res.status === 404\n ? 'Repository search is not offered on this deployment (managed cloud only).'\n : 'Check backendUrl points at the Agora API base and that Clio is available.',\n });\n }\n return (await res.json()) as AthenaRepoSearchResponse;\n}\n\n/**\n * Search the indexed repositories through TanStack Query with the provider's\n * `backendUrl` and credentials. Keyed by the non-secret request and a\n * credential generation (a counter that advances when the host's credentials\n * change), never by the credential itself. Uses the host's `QueryClient` when\n * one is in context (`enableThreadList` mounts the SDK's own) and a\n * hook-scoped client otherwise, so it works under any `<AthenaProvider>`.\n */\nexport function useAthenaRepoSearch({\n enabled = true,\n ...params\n}: UseAthenaRepoSearchOptions): UseQueryResult<AthenaRepoSearchResponse, Error> {\n const config = useAthenaConfig();\n const { backendUrl, apiKey, getToken } = config;\n const credentialsRef = useRef(credentialShape({ apiKey, token: config.token, getToken }));\n const generationRef = useRef(0);\n const currentCredentials = credentialShape({ apiKey, token: config.token, getToken });\n if (!sameCredentialShape(credentialsRef.current, currentCredentials)) {\n credentialsRef.current = currentCredentials;\n generationRef.current += 1;\n }\n const body = repoSearchRequestBody(params);\n const contextClient = useContext(QueryClientContext);\n const [fallbackClient] = useState(() => (contextClient ? undefined : new QueryClient()));\n return useQuery(\n {\n queryKey: ['athena-sdk', 'repo-search', backendUrl, generationRef.current, body],\n // `config.token` is read at fetch time: the provider serves the latest\n // token through a getter, so rotations reach the request.\n queryFn: ({ signal }) =>\n fetchRepoSearch(backendUrl, { apiKey, token: config.token, getToken }, params, { signal }),\n enabled: enabled && body.query.length > 0 && backendUrl.length > 0,\n staleTime: 30_000,\n placeholderData: keepPreviousData,\n refetchOnWindowFocus: false,\n },\n contextClient ?? fallbackClient,\n );\n}\n"],"names":["getAthenaApiBaseUrl","AthenaSdkError","sendWithCredentialRenewal","useAthenaConfig","useRef","credentialShape","sameCredentialShape","useContext","QueryClientContext","useState","QueryClient","useQuery","keepPreviousData"],"mappings":"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"}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* cloud today: a non-staff caller reads `403`, a private deployment `404`.
|
|
8
8
|
*/
|
|
9
9
|
import { type UseQueryResult } from '@tanstack/react-query';
|
|
10
|
-
import {
|
|
10
|
+
import type { StatewireAuthSource } from '../runtime/statewire-auth-headers';
|
|
11
11
|
export type AthenaRepoSearchMode = 'hybrid' | 'lexical';
|
|
12
12
|
/** One file range of one indexed repository, as the route serves it. */
|
|
13
13
|
export interface AthenaRepoSearchHit {
|
|
@@ -74,12 +74,12 @@ export declare function repoSearchRequestBody(params: AthenaRepoSearchParams): A
|
|
|
74
74
|
* One search request. The credential rides `Authorization: Bearer` (a token,
|
|
75
75
|
* resolved live through `getToken` when the host provided one) or
|
|
76
76
|
* `X-API-KEY`; with neither, a same-origin host's session cookie rides
|
|
77
|
-
* automatically, as the SDK's other REST helpers allow. A
|
|
78
|
-
* forced re-mint through `getToken` (`force: true`, the
|
|
79
|
-
* transport's `staleAuth` contract) — a
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* own identity.
|
|
77
|
+
* automatically, as the SDK's other REST helpers allow. A refused bearer
|
|
78
|
+
* (`401`) gets one forced re-mint through `getToken` (`force: true`, the
|
|
79
|
+
* statewire transport's `staleAuth` contract; `sendWithCredentialRenewal`) — a
|
|
80
|
+
* getter that caches until forced would otherwise resend the same expired
|
|
81
|
+
* token on every retry. Every failure is an `AthenaSdkError`, transport
|
|
82
|
+
* rejections included; only an abort keeps its own identity.
|
|
83
83
|
*/
|
|
84
84
|
export declare function fetchRepoSearch(backendUrl: string, auth: StatewireAuthSource, params: AthenaRepoSearchParams, opts?: {
|
|
85
85
|
signal?: AbortSignal;
|
|
@@ -3,7 +3,7 @@ import { useRef, useContext, useState } from "react";
|
|
|
3
3
|
import { getAthenaApiBaseUrl } from "../collab/mint.js";
|
|
4
4
|
import { AthenaSdkError } from "../diagnostics/errors.js";
|
|
5
5
|
import { useAthenaConfig } from "../provider/AthenaContext.js";
|
|
6
|
-
import {
|
|
6
|
+
import { sendWithCredentialRenewal } from "../runtime/credential-renewal.js";
|
|
7
7
|
import { credentialShape, sameCredentialShape } from "../threads/credential-shape.js";
|
|
8
8
|
const ATHENA_REPO_SEARCH_PATH = "/api/search/repositories";
|
|
9
9
|
function repoSearchRequestBody(params) {
|
|
@@ -22,8 +22,7 @@ function isAbortError(error) {
|
|
|
22
22
|
async function fetchRepoSearch(backendUrl, auth, params, opts = {}) {
|
|
23
23
|
const url = `${getAthenaApiBaseUrl(backendUrl)}${ATHENA_REPO_SEARCH_PATH}`;
|
|
24
24
|
const body = JSON.stringify(repoSearchRequestBody(params));
|
|
25
|
-
const post = async (
|
|
26
|
-
const headers = await resolveStatewireAuthHeaders(auth, { staleAuth });
|
|
25
|
+
const post = async (headers) => {
|
|
27
26
|
try {
|
|
28
27
|
return await fetch(url, {
|
|
29
28
|
method: "POST",
|
|
@@ -41,8 +40,7 @@ async function fetchRepoSearch(backendUrl, auth, params, opts = {}) {
|
|
|
41
40
|
});
|
|
42
41
|
}
|
|
43
42
|
};
|
|
44
|
-
|
|
45
|
-
if (res.status === 401 && auth.getToken) res = await post(true);
|
|
43
|
+
const res = await sendWithCredentialRenewal(auth, post);
|
|
46
44
|
if (!res.ok) {
|
|
47
45
|
const refused = res.status === 401 || res.status === 403;
|
|
48
46
|
throw new AthenaSdkError({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAthenaRepoSearch.js","sources":["../../src/search/useAthenaRepoSearch.ts"],"sourcesContent":["/**\n * Code search over the workspace's indexed repositories —\n * `POST /api/search/repositories` (agora `web/api/search/repositories.py`),\n * the HTTP face of Clio's repository source for a Devin-style \"Ask\" surface\n * that searches the monorepo without an agent turn. Athena platform staff\n * only (the account-level `is_admin`, not a workspace role) on the managed\n * cloud today: a non-staff caller reads `403`, a private deployment `404`.\n */\nimport {\n keepPreviousData,\n QueryClient,\n QueryClientContext,\n useQuery,\n type UseQueryResult,\n} from '@tanstack/react-query';\nimport { useContext, useRef, useState } from 'react';\nimport { getAthenaApiBaseUrl } from '../collab/mint';\nimport { AthenaSdkError } from '../diagnostics/errors';\nimport { useAthenaConfig } from '../provider/AthenaContext';\nimport {\n resolveStatewireAuthHeaders,\n type StatewireAuthSource,\n} from '../runtime/statewire-auth-headers';\nimport { credentialShape, sameCredentialShape } from '../threads/credential-shape';\n\nexport type AthenaRepoSearchMode = 'hybrid' | 'lexical';\n\n/** One file range of one indexed repository, as the route serves it. */\nexport interface AthenaRepoSearchHit {\n /** The GitHub catalog asset the repository is connected through. */\n catalogAssetId: string;\n /** `owner/repo`; `null` when the catalog names no repository. */\n repoFullName: string | null;\n /** Repo-relative, `/`-separated. */\n path: string;\n /** 1-based, inclusive. */\n startLine: number;\n endLine: number;\n /** The enclosing declaration (`Class.method`) when the chunk's header named one. */\n symbol: string | null;\n /** Clio's language name for the file (`python`, `typescript`, …). */\n language: string | null;\n /** `code` (an AST chunk) or `window` (a Markdown heading window). */\n unitType: string;\n score: number;\n /** The indexed commit the hit was read at. */\n commitSha: string | null;\n /** `{origin}/{owner}/{repo}/blob/{sha}/{path}#L{a}-L{b}`, GHES-aware. */\n githubUrl: string | null;\n /** The chunk's lines, header line removed, line breaks preserved. */\n snippet: string;\n /** `asset_<catalog>?anchor=file_line_range&…` — what `read_asset` and a citation take. */\n readAssetReference: string;\n}\n\nexport interface AthenaRepoSearchResponse {\n /** Best first, at most `limit`. */\n hits: AthenaRepoSearchHit[];\n tookMs: number;\n mode: AthenaRepoSearchMode;\n}\n\nexport interface AthenaRepoSearchParams {\n query: string;\n /** 1–25; the route's default is 10. */\n limit?: number;\n /** `hybrid` (default) or `lexical`. */\n mode?: AthenaRepoSearchMode;\n /** Search these GitHub catalog assets only; empty means every indexed repository. */\n repositoryAssetIds?: string[];\n /** gitignore-style globs a hit's path must match (`agora/**\\/*.py`, `docs/`). */\n paths?: string[];\n /** Clio language names a hit's file must be indexed as. */\n languages?: string[];\n}\n\nexport interface UseAthenaRepoSearchOptions extends AthenaRepoSearchParams {\n /** `false` holds the query idle (a blank `query` does too). Default `true`. */\n enabled?: boolean;\n}\n\n/** The route's JSON body — snake_case, unset filters omitted. */\nexport interface AthenaRepoSearchRequestBody {\n query: string;\n limit?: number;\n mode?: AthenaRepoSearchMode;\n repository_asset_ids?: string[];\n paths?: string[];\n languages?: string[];\n}\n\nexport const ATHENA_REPO_SEARCH_PATH = '/api/search/repositories';\n\nexport function repoSearchRequestBody(params: AthenaRepoSearchParams): AthenaRepoSearchRequestBody {\n const body: AthenaRepoSearchRequestBody = { query: params.query.trim() };\n if (params.limit !== undefined) body.limit = params.limit;\n if (params.mode !== undefined) body.mode = params.mode;\n if (params.repositoryAssetIds?.length) body.repository_asset_ids = params.repositoryAssetIds;\n if (params.paths?.length) body.paths = params.paths;\n if (params.languages?.length) body.languages = params.languages;\n return body;\n}\n\n/** A `fetch` the caller cancelled (unmount, a newer query): lifecycle, not failure. */\nfunction isAbortError(error: unknown): boolean {\n return (error instanceof DOMException || error instanceof Error) && error.name === 'AbortError';\n}\n\n/**\n * One search request. The credential rides `Authorization: Bearer` (a token,\n * resolved live through `getToken` when the host provided one) or\n * `X-API-KEY`; with neither, a same-origin host's session cookie rides\n * automatically, as the SDK's other REST helpers allow. A `401` gets one\n * forced re-mint through `getToken` (`force: true`, the statewire\n * transport's `staleAuth` contract) — a getter that caches until forced would\n * otherwise resend the same expired token on every retry. Every failure is an\n * `AthenaSdkError`, transport rejections included; only an abort keeps its\n * own identity.\n */\nexport async function fetchRepoSearch(\n backendUrl: string,\n auth: StatewireAuthSource,\n params: AthenaRepoSearchParams,\n opts: { signal?: AbortSignal } = {},\n): Promise<AthenaRepoSearchResponse> {\n const url = `${getAthenaApiBaseUrl(backendUrl)}${ATHENA_REPO_SEARCH_PATH}`;\n const body = JSON.stringify(repoSearchRequestBody(params));\n const post = async (staleAuth: boolean): Promise<Response> => {\n const headers = await resolveStatewireAuthHeaders(auth, { staleAuth });\n try {\n return await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body,\n signal: opts.signal ?? null,\n });\n } catch (error) {\n if (isAbortError(error)) throw error;\n throw new AthenaSdkError({\n code: 'repo_search_failed',\n message: 'Repository search could not reach the Agora API.',\n cause: error,\n hint: 'Check the network and that backendUrl points at the Agora API base.',\n });\n }\n };\n let res = await post(false);\n if (res.status === 401 && auth.getToken) res = await post(true);\n if (!res.ok) {\n const refused = res.status === 401 || res.status === 403;\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'repo_search_failed',\n message: `Repository search failed: ${res.status}`,\n status: res.status,\n detail: await res.json().catch(() => undefined),\n hint: refused\n ? 'Repository search is limited to Athena platform staff on the managed cloud; check the credential.'\n : res.status === 404\n ? 'Repository search is not offered on this deployment (managed cloud only).'\n : 'Check backendUrl points at the Agora API base and that Clio is available.',\n });\n }\n return (await res.json()) as AthenaRepoSearchResponse;\n}\n\n/**\n * Search the indexed repositories through TanStack Query with the provider's\n * `backendUrl` and credentials. Keyed by the non-secret request and a\n * credential generation (a counter that advances when the host's credentials\n * change), never by the credential itself. Uses the host's `QueryClient` when\n * one is in context (`enableThreadList` mounts the SDK's own) and a\n * hook-scoped client otherwise, so it works under any `<AthenaProvider>`.\n */\nexport function useAthenaRepoSearch({\n enabled = true,\n ...params\n}: UseAthenaRepoSearchOptions): UseQueryResult<AthenaRepoSearchResponse, Error> {\n const config = useAthenaConfig();\n const { backendUrl, apiKey, getToken } = config;\n const credentialsRef = useRef(credentialShape({ apiKey, token: config.token, getToken }));\n const generationRef = useRef(0);\n const currentCredentials = credentialShape({ apiKey, token: config.token, getToken });\n if (!sameCredentialShape(credentialsRef.current, currentCredentials)) {\n credentialsRef.current = currentCredentials;\n generationRef.current += 1;\n }\n const body = repoSearchRequestBody(params);\n const contextClient = useContext(QueryClientContext);\n const [fallbackClient] = useState(() => (contextClient ? undefined : new QueryClient()));\n return useQuery(\n {\n queryKey: ['athena-sdk', 'repo-search', backendUrl, generationRef.current, body],\n // `config.token` is read at fetch time: the provider serves the latest\n // token through a getter, so rotations reach the request.\n queryFn: ({ signal }) =>\n fetchRepoSearch(backendUrl, { apiKey, token: config.token, getToken }, params, { signal }),\n enabled: enabled && body.query.length > 0 && backendUrl.length > 0,\n staleTime: 30_000,\n placeholderData: keepPreviousData,\n refetchOnWindowFocus: false,\n },\n contextClient ?? fallbackClient,\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useAthenaRepoSearch.js","sources":["../../src/search/useAthenaRepoSearch.ts"],"sourcesContent":["/**\n * Code search over the workspace's indexed repositories —\n * `POST /api/search/repositories` (agora `web/api/search/repositories.py`),\n * the HTTP face of Clio's repository source for a Devin-style \"Ask\" surface\n * that searches the monorepo without an agent turn. Athena platform staff\n * only (the account-level `is_admin`, not a workspace role) on the managed\n * cloud today: a non-staff caller reads `403`, a private deployment `404`.\n */\nimport {\n keepPreviousData,\n QueryClient,\n QueryClientContext,\n useQuery,\n type UseQueryResult,\n} from '@tanstack/react-query';\nimport { useContext, useRef, useState } from 'react';\nimport { getAthenaApiBaseUrl } from '../collab/mint';\nimport { AthenaSdkError } from '../diagnostics/errors';\nimport { useAthenaConfig } from '../provider/AthenaContext';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport type { StatewireAuthSource } from '../runtime/statewire-auth-headers';\nimport { credentialShape, sameCredentialShape } from '../threads/credential-shape';\n\nexport type AthenaRepoSearchMode = 'hybrid' | 'lexical';\n\n/** One file range of one indexed repository, as the route serves it. */\nexport interface AthenaRepoSearchHit {\n /** The GitHub catalog asset the repository is connected through. */\n catalogAssetId: string;\n /** `owner/repo`; `null` when the catalog names no repository. */\n repoFullName: string | null;\n /** Repo-relative, `/`-separated. */\n path: string;\n /** 1-based, inclusive. */\n startLine: number;\n endLine: number;\n /** The enclosing declaration (`Class.method`) when the chunk's header named one. */\n symbol: string | null;\n /** Clio's language name for the file (`python`, `typescript`, …). */\n language: string | null;\n /** `code` (an AST chunk) or `window` (a Markdown heading window). */\n unitType: string;\n score: number;\n /** The indexed commit the hit was read at. */\n commitSha: string | null;\n /** `{origin}/{owner}/{repo}/blob/{sha}/{path}#L{a}-L{b}`, GHES-aware. */\n githubUrl: string | null;\n /** The chunk's lines, header line removed, line breaks preserved. */\n snippet: string;\n /** `asset_<catalog>?anchor=file_line_range&…` — what `read_asset` and a citation take. */\n readAssetReference: string;\n}\n\nexport interface AthenaRepoSearchResponse {\n /** Best first, at most `limit`. */\n hits: AthenaRepoSearchHit[];\n tookMs: number;\n mode: AthenaRepoSearchMode;\n}\n\nexport interface AthenaRepoSearchParams {\n query: string;\n /** 1–25; the route's default is 10. */\n limit?: number;\n /** `hybrid` (default) or `lexical`. */\n mode?: AthenaRepoSearchMode;\n /** Search these GitHub catalog assets only; empty means every indexed repository. */\n repositoryAssetIds?: string[];\n /** gitignore-style globs a hit's path must match (`agora/**\\/*.py`, `docs/`). */\n paths?: string[];\n /** Clio language names a hit's file must be indexed as. */\n languages?: string[];\n}\n\nexport interface UseAthenaRepoSearchOptions extends AthenaRepoSearchParams {\n /** `false` holds the query idle (a blank `query` does too). Default `true`. */\n enabled?: boolean;\n}\n\n/** The route's JSON body — snake_case, unset filters omitted. */\nexport interface AthenaRepoSearchRequestBody {\n query: string;\n limit?: number;\n mode?: AthenaRepoSearchMode;\n repository_asset_ids?: string[];\n paths?: string[];\n languages?: string[];\n}\n\nexport const ATHENA_REPO_SEARCH_PATH = '/api/search/repositories';\n\nexport function repoSearchRequestBody(params: AthenaRepoSearchParams): AthenaRepoSearchRequestBody {\n const body: AthenaRepoSearchRequestBody = { query: params.query.trim() };\n if (params.limit !== undefined) body.limit = params.limit;\n if (params.mode !== undefined) body.mode = params.mode;\n if (params.repositoryAssetIds?.length) body.repository_asset_ids = params.repositoryAssetIds;\n if (params.paths?.length) body.paths = params.paths;\n if (params.languages?.length) body.languages = params.languages;\n return body;\n}\n\n/** A `fetch` the caller cancelled (unmount, a newer query): lifecycle, not failure. */\nfunction isAbortError(error: unknown): boolean {\n return (error instanceof DOMException || error instanceof Error) && error.name === 'AbortError';\n}\n\n/**\n * One search request. The credential rides `Authorization: Bearer` (a token,\n * resolved live through `getToken` when the host provided one) or\n * `X-API-KEY`; with neither, a same-origin host's session cookie rides\n * automatically, as the SDK's other REST helpers allow. A refused bearer\n * (`401`) gets one forced re-mint through `getToken` (`force: true`, the\n * statewire transport's `staleAuth` contract; `sendWithCredentialRenewal`) — a\n * getter that caches until forced would otherwise resend the same expired\n * token on every retry. Every failure is an `AthenaSdkError`, transport\n * rejections included; only an abort keeps its own identity.\n */\nexport async function fetchRepoSearch(\n backendUrl: string,\n auth: StatewireAuthSource,\n params: AthenaRepoSearchParams,\n opts: { signal?: AbortSignal } = {},\n): Promise<AthenaRepoSearchResponse> {\n const url = `${getAthenaApiBaseUrl(backendUrl)}${ATHENA_REPO_SEARCH_PATH}`;\n const body = JSON.stringify(repoSearchRequestBody(params));\n const post = async (headers: Record<string, string>): Promise<Response> => {\n try {\n return await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body,\n signal: opts.signal ?? null,\n });\n } catch (error) {\n if (isAbortError(error)) throw error;\n throw new AthenaSdkError({\n code: 'repo_search_failed',\n message: 'Repository search could not reach the Agora API.',\n cause: error,\n hint: 'Check the network and that backendUrl points at the Agora API base.',\n });\n }\n };\n const res = await sendWithCredentialRenewal(auth, post);\n if (!res.ok) {\n const refused = res.status === 401 || res.status === 403;\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'repo_search_failed',\n message: `Repository search failed: ${res.status}`,\n status: res.status,\n detail: await res.json().catch(() => undefined),\n hint: refused\n ? 'Repository search is limited to Athena platform staff on the managed cloud; check the credential.'\n : res.status === 404\n ? 'Repository search is not offered on this deployment (managed cloud only).'\n : 'Check backendUrl points at the Agora API base and that Clio is available.',\n });\n }\n return (await res.json()) as AthenaRepoSearchResponse;\n}\n\n/**\n * Search the indexed repositories through TanStack Query with the provider's\n * `backendUrl` and credentials. Keyed by the non-secret request and a\n * credential generation (a counter that advances when the host's credentials\n * change), never by the credential itself. Uses the host's `QueryClient` when\n * one is in context (`enableThreadList` mounts the SDK's own) and a\n * hook-scoped client otherwise, so it works under any `<AthenaProvider>`.\n */\nexport function useAthenaRepoSearch({\n enabled = true,\n ...params\n}: UseAthenaRepoSearchOptions): UseQueryResult<AthenaRepoSearchResponse, Error> {\n const config = useAthenaConfig();\n const { backendUrl, apiKey, getToken } = config;\n const credentialsRef = useRef(credentialShape({ apiKey, token: config.token, getToken }));\n const generationRef = useRef(0);\n const currentCredentials = credentialShape({ apiKey, token: config.token, getToken });\n if (!sameCredentialShape(credentialsRef.current, currentCredentials)) {\n credentialsRef.current = currentCredentials;\n generationRef.current += 1;\n }\n const body = repoSearchRequestBody(params);\n const contextClient = useContext(QueryClientContext);\n const [fallbackClient] = useState(() => (contextClient ? undefined : new QueryClient()));\n return useQuery(\n {\n queryKey: ['athena-sdk', 'repo-search', backendUrl, generationRef.current, body],\n // `config.token` is read at fetch time: the provider serves the latest\n // token through a getter, so rotations reach the request.\n queryFn: ({ signal }) =>\n fetchRepoSearch(backendUrl, { apiKey, token: config.token, getToken }, params, { signal }),\n enabled: enabled && body.query.length > 0 && backendUrl.length > 0,\n staleTime: 30_000,\n placeholderData: keepPreviousData,\n refetchOnWindowFocus: false,\n },\n contextClient ?? fallbackClient,\n );\n}\n"],"names":[],"mappings":"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"}
|