Commit Graph

1332 Commits

Author SHA1 Message Date
Sarah Wooders
3fdf2b6c79 chore: deprecate old agent messaging (#9120) 2026-02-24 10:52:06 -08:00
jnjpng
e25a0c9cdf feat: update compact endpoint to store summary message (#9215)
* base

* add tests
2026-02-24 10:52:06 -08:00
jnjpng
d28ccc0be6 feat: add summary message and event on compaction (#9144)
* base

* update

* update

* revert formatting

* routes

* legacy

* fix

* review

* update
2026-02-24 10:52:05 -08:00
Ari Webb
7b0b1f2531 fix: warning (#9179)
* fix: warning

* just stage publish api

* note

* api
2026-02-24 10:52:05 -08:00
cthomas
d992aa0df4 fix: non-streaming conversation messages endpoint (#9159)
* fix: non-streaming conversation messages endpoint

**Problems:**
1. `AssertionError: run_id is required when enforce_run_id_set is True`
   - Non-streaming path didn't create a run before calling `step()`

2. `ResponseValidationError: Unable to extract tag using discriminator 'message_type'`
   - `response_model=LettaStreamingResponse` but non-streaming returns `LettaResponse`

**Fixes:**
1. Add run creation before calling `step()` (mirrors agents endpoint)
2. Set run_id in Redis for cancellation support
3. Pass `run_id` to `step()`
4. Change `response_model` from `LettaStreamingResponse` to `LettaResponse`
   (streaming returns `StreamingResponse` which bypasses response_model validation)

**Test:**
Added `test_conversation_non_streaming_raw_http` to verify the fix.

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* api sync

---------

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
Shubham Naik
bb2145c24c connections (#9113)
* chore: release code

* chore: release code

* chore: release code

* chore: release code

* chore: release code

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: change paths

* chore: remote

* chore: support  multi project chat
2026-01-29 12:44:04 -08:00
Kian Jones
34eed72150 feat: add user id validation (#9128)
* add user id validation

* relax conversation id check to allow default while I'm here

* fix annotation validation

* -api changes
2026-01-29 12:44:04 -08:00
Kian Jones
0099a95a43 fix(sec): first pass of ensuring actor id is required everywhere (#9126)
first pass of ensuring actor id is required
2026-01-29 12:44:04 -08:00
github-actions[bot]
62a00cc672 fix: remove deprecation from agent passages endpoints (#9117)
* fix: remove deprecation from agent passages endpoints

The client.agent.passages endpoints (list, create, search, delete) were
incorrectly marked as deprecated. This would break significant amounts
of user code and negatively impact developer experience.

Fixes #9116

Co-authored-by: Ari Webb <AriWebb@users.noreply.github.com>

* stage publish api

---------

Co-authored-by: letta-code <248085862+letta-code@users.noreply.github.com>
Co-authored-by: Ari Webb <AriWebb@users.noreply.github.com>
Co-authored-by: Ari Webb <ari@letta.com>
2026-01-29 12:44:04 -08:00
github-actions[bot]
194c743223 refactor: rename stream to streaming in ConversationMessageRequest (#9063) 2026-01-29 12:44:04 -08:00
github-actions[bot]
1d1bb29a43 feat: add override_model support for agent file import (#9058) 2026-01-29 12:44:04 -08:00
Sarah Wooders
6c415b27f8 feat: add non-streaming option for conversation messages (#9044)
* feat: add non-streaming option for conversation messages

- Add ConversationMessageRequest with stream=True default (backwards compatible)
- stream=true (default): SSE streaming via StreamingService
- stream=false: JSON response via AgentLoop.load().step()

🤖 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* chore: regenerate API schema for ConversationMessageRequest

---------

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
cthomas
ca40eff7bc fix: ensure stop_reason is always set when marking runs as failed (#9045)
**Problem:**
Production error showed runs being marked as failed with stop_reason=None,
which violates LettaStopReason's Pydantic schema (requires valid enum value).
This caused cascading validation errors that got stored in metadata.

Example error:
```
Run is already in a terminal state failed with stop reason None, but is being
updated with data {'status': 'failed', 'stop_reason': None, 'metadata':
{'error': "1 validation error for LettaStopReason\nstop_reason Input should
be 'end_turn', 'error', ... [type=enum, input_value=None]"}}
```

**Root Causes:**
1. routers/v1/agents.py had 3 exception handlers creating RunUpdate(status=failed)
   without stop_reason
2. Success path assumed result.stop_reason always exists (AttributeError if None)
3. run_manager.py tried to create LettaStopReason(stop_reason=None) when
   refreshing result messages

**Fixes:**
1. Added stop_reason=StopReasonType.error to 3 exception handlers
2. Added defensive None checks before accessing result.stop_reason.stop_reason
3. Added fallback to StopReasonType.error when pydantic_run.stop_reason is None

**Trigger:**
OpenAI BadRequestError for invalid tool schema → exception handlers marked
run as failed without stop_reason → validation error when constructing response

👾 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
Sarah Wooders
25e9539a6e feat: add batch passage create and optional search query (#8866) 2026-01-29 12:44:04 -08:00
cthomas
c162de5127 fix: use shared event + .athrow() to properly set stream_was_cancelle… (#9019)
fix: use shared event + .athrow() to properly set stream_was_cancelled flag

**Problem:**
When a run is cancelled via /cancel endpoint, `stream_was_cancelled` remained
False because `RunCancelledException` was raised in the consumer code (wrapper),
which closes the generator from outside. This causes Python to skip the
generator's except blocks and jump directly to finally with the wrong flag value.

**Solution:**
1. Shared `asyncio.Event` registry for cross-layer cancellation signaling
2. `cancellation_aware_stream_wrapper` sets the event when cancellation detected
3. Wrapper uses `.athrow()` to inject exception INTO generator (not consumer-side raise)
4. All streaming interfaces check event in `finally` block to set flag correctly
5. `streaming_service.py` handles `RunCancelledException` gracefully, yields [DONE]

**Changes:**
- streaming_response.py: Event registry + .athrow() injection + graceful handling
- openai_streaming_interface.py: 3 classes check event in finally
- gemini_streaming_interface.py: Check event in finally
- anthropic_*.py: Catch RunCancelledException
- simple_llm_stream_adapter.py: Create & pass event to interfaces
- streaming_service.py: Handle RunCancelledException, yield [DONE], skip double-update
- routers/v1/{conversations,runs}.py: Pass event to wrapper
- integration_test_human_in_the_loop.py: New test for approval + cancellation

**Tests:**
- test_tool_call with cancellation (OpenAI models) 
- test_approve_with_cancellation (approval flow + concurrent cancel) 

**Known cosmetic warnings (pre-existing):**
- "Run already in terminal state" - agent loop tries to update after /cancel
- "Stream ended without terminal event" - background streaming timing race

👾 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
2026-01-29 12:44:04 -08:00
Ari Webb
5c06918042 fix: don't need embedding model for self hosted [LET-7009] (#8935)
* fix: don't need embedding model for self hosted

* stage publish api

* passes tests

* add test

* remove unnecessary upgrades

* update revision order db migrations

* add timeout for ci
2026-01-29 12:44:04 -08:00
Kian Jones
7133083b81 fix: agent_tags for provider traces (#8989)
* add include tags

* include agent_tags and pass them into the adapter
2026-01-29 12:43:53 -08:00
Ari Webb
4ec6649caf feat: byok provider models in db also (#8317)
* feat: byok provider models in db also

* make tests and sync api

* fix inconsistent state with recreating provider of same name

* fix sync on byok creation

* update revision

* move stripe code for testing purposes

* revert

* add refresh byok models endpoint

* just stage publish api

* add tests

* reorder revision

* add test for name clashes
2026-01-29 12:43:53 -08:00
Kian Jones
a92e868ee6 feat: centralize telemetry logging at LLM client level (#8815)
* feat: centralize telemetry logging at LLM client level

Moves telemetry logging from individual adapters to LLMClientBase:
- Add TelemetryStreamWrapper for streaming telemetry on stream close
- Add request_async_with_telemetry() for non-streaming requests
- Add stream_async_with_telemetry() for streaming requests
- Add set_telemetry_context() to configure agent_id, run_id, step_id

Updates adapters and agents to use new pattern:
- LettaLLMAdapter now accepts agent_id/run_id in constructor
- Adapters call set_telemetry_context() before LLM requests
- Removes duplicate telemetry logging from adapters
- Enriches traces with agent_id, run_id, call_type metadata

🐙 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* fix: accumulate streaming response content for telemetry

TelemetryStreamWrapper now extracts actual response data from chunks:
- Content text (concatenated from deltas)
- Tool calls (id, name, arguments)
- Model name, finish reason, usage stats

🐙 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* refactor: move streaming telemetry to caller (option 3)

- Remove TelemetryStreamWrapper class
- Add log_provider_trace_async() helper to LLMClientBase
- stream_async_with_telemetry() now just returns raw stream
- Callers log telemetry after processing with rich interface data

Updated callers:
- summarizer.py: logs content + usage after stream processing
- letta_agent.py: logs tool_call, reasoning, model, usage

🐙 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* fix: pass agent_id and run_id to parent adapter class

LettaLLMStreamAdapter was not passing agent_id/run_id to parent,
causing "unexpected keyword argument" errors.

🐙 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

---------

Co-authored-by: Letta <noreply@letta.com>
2026-01-19 15:54:43 -08:00
Kian Jones
9418ab9815 feat: add provider trace backend abstraction for multi-backend telemetry (#8814)
* feat: add provider trace backend abstraction for multi-backend telemetry

Introduces a pluggable backend system for provider traces:
- Base class with async/sync create and read interfaces
- PostgreSQL backend (existing behavior)
- ClickHouse backend (via OTEL instrumentation)
- Socket backend (writes to Unix socket for crouton sidecar)
- Factory for instantiating backends from config

Refactors TelemetryManager to use backends with support for:
- Multi-backend writes (concurrent via asyncio.gather)
- Primary backend for reads (first in config list)
- Graceful error handling per backend

Config: LETTA_TELEMETRY_PROVIDER_TRACE_BACKEND (comma-separated)
Example: "postgres,socket" for dual-write to Postgres and crouton

🐙 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* feat: add protocol version to socket backend records

Adds PROTOCOL_VERSION constant to socket backend:
- Included in every telemetry record sent to crouton
- Must match ProtocolVersion in apps/crouton/main.go
- Enables crouton to detect and reject incompatible messages

🐙 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* fix: remove organization_id from ProviderTraceCreate calls

The organization_id is now handled via the actor parameter in the
telemetry manager, not through ProviderTraceCreate schema. This fixes
validation errors after changing ProviderTraceCreate to inherit from
BaseProviderTrace which forbids extra fields.

🐙 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* consolidate provider trace

* add clickhouse-connect to fix bug on main lmao

* auto generated sdk changes, and deployment details, and clikchouse prefix bug and added fields to runs trace return api

* auto generated sdk changes, and deployment details, and clikchouse prefix bug and added fields to runs trace return api

* consolidate provider trace

* consolidate provider trace bug fix

---------

Co-authored-by: Letta <noreply@letta.com>
2026-01-19 15:54:43 -08:00
Sarah Wooders
5c7bed7743 feat: add conversation_id to export export and compact (#8792) 2026-01-19 15:54:43 -08:00
jnjpng
5017cb1d12 feat: add chatgpt oauth client for codex routing (#8774)
* base

* refresh

* use default model fallback

* patch

* streaming

* generate
2026-01-19 15:54:42 -08:00
Ari Webb
193c0e4b74 feat: add override_model to message endpoints (#8763)
* feat: add override_model to message endpoints

* add tests back

* remove from ci
2026-01-19 15:54:42 -08:00
Kian Jones
d2c3350a7e feat(runs): add run ID filter to runs page (#8726)
feat(core): add run_id filter to internal runs endpoint

Adds the ability to filter runs by a specific run ID in the
internal runs list endpoint.

🐛 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
2026-01-19 15:54:42 -08:00
Charles Packer
97f7e95d1d feat: add PATCH route for updating conversation summary (#8322) 2026-01-19 15:54:41 -08:00
Sarah Wooders
f91e77d971 fix: add cancel for conversations to SDK (#8742) 2026-01-19 15:54:41 -08:00
jnjpng
58a5375c19 fix: test sdk client due to message batch route ordering (#8733)
* base

* generate
2026-01-19 15:54:40 -08:00
Sarah Wooders
aabd58628e feat: add conversation cancellation endpoint (#8729) 2026-01-19 15:54:40 -08:00
jnjpng
037c20ae1b feat: query param parity for conversation messages (#8730)
* base

* add tests

* generate
2026-01-19 15:54:40 -08:00
Sarah Wooders
9aac2abdfe chore: deprecate identities/groups APIs and remove from SDK (#8580)
* chore: deprecate identities/groups APIs and remove from SDK

- Mark all /v1/identities/* endpoints as deprecated
- Mark all /v1/groups/* endpoints as deprecated
- Remove identities, groups, and batches resources from stainless.yml
- Batch API remains active but hidden from SDK

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* chore: update autogenerated SDK files

* chore: regenerate SDK and OpenAPI spec

Run `just stage-api` and `just publish-api` to sync generated files.

👾 Generated with [Letta Code](https://letta.com)

Co-authored-by: Sarah Wooders <sarahwooders@users.noreply.github.com>

* chore: remove schedule API from stainless SDK

Remove schedule subresource from stainless.yml to hide scheduled messages
endpoints from the SDK generation.

👾 Generated with [Letta Code](https://letta.com)

Co-authored-by: Sarah Wooders <sarahwooders@users.noreply.github.com>

---------

Co-authored-by: Letta <noreply@letta.com>
Co-authored-by: letta-code <248085862+letta-code@users.noreply.github.com>
Co-authored-by: Sarah Wooders <sarahwooders@users.noreply.github.com>
2026-01-19 15:54:40 -08:00
jnjpng
e3e758a8c0 feat: add retrieve message endpoint and to client sdk (#8719)
* base

* generate openapi

* try again

* now
2026-01-19 15:54:40 -08:00
Kian Jones
3eae81cf62 feat: add /v1/runs/{run_id}/trace endpoint for OTEL traces (#8682)
* feat: add /v1/runs/{run_id}/trace endpoint for OTEL traces

- Add new endpoint to retrieve filtered OTEL spans for a run
- Filter to only return UI-relevant spans (agent_step, tool executions, root span, TTFT)
- Skip Postgres writes when ClickHouse is enabled for provider traces
- Add USE_CLICKHOUSE_FOR_PROVIDER_TRACES env var to helm/justfile
- Move typecheck CI to self-hosted runners

🤖 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* fix: add missing clickhouse_provider_traces.py

The telemetry_manager.py imports ClickhouseProviderTraceReader from
this module, but the file was not included when splitting the PR.

🤖 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* autogen

* fix: add trace.retrieve to stainless.yml for SDK generation

Adds the runs.trace.retrieve method mapping so Stainless generates
the useRunsServiceRetrieveTraceForRun hook.

🤖 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

---------

Co-authored-by: Letta <noreply@letta.com>
2026-01-19 15:54:39 -08:00
Sarah Wooders
9c4f191755 feat: add conversation_id parameter to context endpoint [LET-6989] (#8678)
* feat: add conversation_id parameter to context endpoint [LET-6989]

Add optional conversation_id query parameter to retrieve_agent_context_window.
When provided, the endpoint uses messages from the specific conversation
instead of the agent's default message_ids.

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* chore: regenerate SDK after context endpoint update [LET-6989]

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

---------

Co-authored-by: Letta <noreply@letta.com>
2026-01-19 15:54:39 -08:00
jnjpng
979062114c chore: fix typo and improve MCP OAuth comments (#8629)
- Fix typo "upate" -> "update" in TODO comments (mcp_manager.py, mcp_server_manager.py)
- Improve comments in OAuth callback handler to explain why MCPOAuthSession
  is used directly (callback is unauthenticated, manager requires actor)
- Clean up variable naming in callback handler

🐾 Generated with [Letta Code](https://letta.com)

Co-authored-by: Letta <noreply@letta.com>
2026-01-19 15:54:38 -08:00
cthomas
ab4ccfca31 feat: add tags support to blocks (#8474)
* feat: add tags support to blocks

* fix: add timestamps and org scoping to blocks_tags

Addresses PR feedback:

1. Migration: Added timestamps (created_at, updated_at), soft delete
   (is_deleted), audit fields (_created_by_id, _last_updated_by_id),
   and organization_id to blocks_tags table for filtering support.
   Follows SQLite baseline pattern (composite PK of block_id+tag, no
   separate id column) to avoid insert failures.

2. ORM: Relationship already correct with lazy="raise" to prevent
   implicit joins and passive_deletes=True for efficient CASCADE deletes.

3. Schema: Changed normalize_tags() from Any to dict for type safety.

4. SQLite: Added blocks_tags to SQLite baseline schema to prevent
   table-not-found errors.

5. Code: Updated all tag row inserts to include organization_id.

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* fix: add ORM columns and update SQLite baseline for blocks_tags

Fixes test failures (CompileError: Unconsumed column names: organization_id):

1. ORM: Added organization_id, timestamps, audit fields to BlocksTags
   ORM model to match database schema from migrations.

2. SQLite baseline: Added full column set to blocks_tags (organization_id,
   timestamps, audit fields) to match PostgreSQL schema.

3. Test: Added 'tags' to expected Block schema fields.

This ensures SQLite and PostgreSQL have matching schemas and the ORM
can consume all columns that the code inserts.

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* revert change to existing alembic migration

* fix: remove passive_deletes and SQLite support for blocks_tags

1. Removed passive_deletes=True from Block.tags relationship to match
   AgentsTags pattern (neither have ondelete CASCADE in DB schema).

2. Removed SQLite branch from _replace_block_pivot_rows_async since
   blocks_tags table is PostgreSQL-only (migration skips SQLite).

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* api sync

---------

Co-authored-by: Letta <noreply@letta.com>
2026-01-19 15:54:38 -08:00
jnjpng
c550457b60 feat: static redirect callback for mcp server oauth (#8611)
* base

* base

* more

* final

* remove

* pass
2026-01-19 15:54:38 -08:00
Sarah Wooders
b8a6496acb feat: add runs_metrics table (#5169) 2026-01-19 15:51:30 -08:00
cthomas
03a64993cf fix: make file reads async (#8513) 2026-01-12 10:57:49 -08:00
jnjpng
87e939deda feat: add fastmcp v2 client (#8457)
* base

* testing code

* update

* nit
2026-01-12 10:57:49 -08:00
Christina Tong
318498bde3 feat: filter internal runs endpoint by conversation id [LET-6886] (#8437) 2026-01-12 10:57:49 -08:00
Ari Webb
754e750cc5 feat: add conversation_id filter to list runs [LET-6865] (#8404)
feat: add conversation_id filter to list runs
2026-01-12 10:57:48 -08:00
Charles Packer
ed6284cedb feat: Add conversation_id filtering to message endpoints (#8324)
* feat: Add conversation_id filtering to message list and search endpoints

Add optional conversation_id parameter to filter messages by conversation:
- client.agents.messages.list
- client.messages.list
- client.messages.search

Changes:
- Added conversation_id field to MessageSearchRequest and SearchAllMessagesRequest schemas
- Added conversation_id filtering to list_messages in message_manager.py
- Updated get_agent_recall_async and get_all_messages_recall_async in server.py
- Added conversation_id query parameter to router endpoints
- Updated Turbopuffer client to support conversation_id filtering in searches

Fixes #8320

🤖 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Charles Packer <cpacker@users.noreply.github.com>

* add conversation_id to message and tpuf

* default messages filter for backward compatibility

* add test and auto gen

* fix integration test

* fix test

* update test

---------

Co-authored-by: letta-code <248085862+letta-code@users.noreply.github.com>
Co-authored-by: Charles Packer <cpacker@users.noreply.github.com>
Co-authored-by: christinatong01 <christina@letta.com>
2026-01-12 10:57:48 -08:00
cthomas
5857b97c7f fix: unbound local variable (#8346) 2026-01-12 10:57:48 -08:00
Sarah Wooders
18a1a16bf4 Revert "feat: add message_types filter to list messages endpoint" (#8314)
Revert "feat: add message_types filter to list messages endpoint (#8280)"

This reverts commit e7ac5df721ec4b3e663dd30239f590ee16bb8630.
2026-01-12 10:57:48 -08:00
Ari Webb
02f3e3f3b9 fix: fix providers and models persistence (#8302) 2026-01-12 10:57:48 -08:00
Charles Packer
e57adc0a6e chore: mark agent.messages.stream endpoint as deprecated (#8227) 2026-01-12 10:57:48 -08:00
Cameron
7c44375cce feat: add message_types filter to list messages endpoint (#8280)
* feat: add message_types filter to list messages endpoint

Add the ability to filter messages by type when listing message history
via GET /v1/agents/{agent_id}/messages. This brings parity with the
create message endpoint which already supports include_return_message_types.

Changes:
- Add message_types query parameter to list_messages endpoint in agents.py
- Add message_types parameter to get_agent_recall_async in server.py
- Filter messages by message_type after LettaMessage conversion
- Add test for message_types filtering

Closes #8277

Written by Cameron ◯ Letta Code

> "Simplicity is the ultimate sophistication." - Leonardo da Vinci

🐙 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* chore: regenerate OpenAPI spec and SDK for message_types filter

🐧 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

Written by Cameron ◯ Letta Code

"The only way to do great work is to love what you do." - Steve Jobs

---------

Co-authored-by: Letta <noreply@letta.com>
2026-01-12 10:57:48 -08:00
Sarah Wooders
87d920782f feat: add conversation and conversation_messages tables for concurrent messaging (#8182) 2026-01-12 10:57:48 -08:00
cthomas
cfde955313 feat: prevent unbounded file queries (#8285) 2026-01-12 10:57:48 -08:00
Charles Packer
64a1a8b14e feat: expose agent_id to the messages search api endpoint (#8252) 2026-01-12 10:57:48 -08:00